Modernizr: Feature Detection Without the Guesswork
Modernizr is a JavaScript library that detects HTML5 and CSS3 features in the user’s browser.
At a glance
- What is it?
- Modernizr generates a custom build that reports which HTML5 and CSS3 features the current browser supports. It fits teams that still ship to uneven browsers, and it is the wrong tool for anyone who can already assume a modern engine.
- Who is it for?
- Adopt Modernizr if you still support browsers whose feature set you cannot assume, and you want detection results as both a global object and html classes. Do not adopt it if your build already targets a single modern engine, because the generated file is dead weight.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Modernizr solves for teams with uneven browsers
Browsers do not agree on which HTML5 and CSS3 features exist. A page that uses a feature the current user agent lacks will fail quietly, and user agent sniffing is a poor substitute because strings lie and get spoofed. Modernizr answers a narrower question: does this browser, right now, support this specific feature? The README states the library tests which native CSS3 and HTML5 features are available in the current UA. The audience is front end developers who want progressive enhancement with granular control, meaning they can ship a baseline experience and layer on richer behaviour only where it is supported. It is not a polyfill library. It reports; you decide what to do with the answer.
How the detection results reach your code
Modernizr exposes results in two places at once. The README says the results are available as properties on a global Modernizr object and as classes on the html element. So a feature test named canvas shows up both as Modernizr.canvas and as a class on the root element, which lets CSS and JavaScript branch on the same fact without duplicating the detection logic. There is also an asynchronous path. The README documents Modernizr.on, which takes a test name and a callback, and states that only events on asynchronous tests are supported, with synchronous tests expected to be handled synchronously. The callback is invoked once per call to on. The README also says the trigger functionality is not exposed, and that if you want control over async tests you should use the src/addTest feature, where any test you add automatically exposes and triggers the on functionality. That is a deliberate boundary: the library keeps the async surface small rather than handing you a general event bus.
Installing Modernizr and producing a first build
The README points away from the project website, which it describes as outdated and broken, and tells you to build your version from npm instead. Clone or download the repository and install dependencies with npm install. The package is also consumed directly as a library: the README shows require("modernizr") exposing a build method whose first argument is a JSON object of options and feature detects, and whose second argument is a callback invoked on completion. The available options are listed in lib/config-all.json.
var modernizr = require("modernizr");
modernizr.build({}, function (result) {
console.log(result); // the build
});An empty options object is the smallest possible call. To see the command line options, the README says to run the binary directly, and to generate everything described in config-all.json you run the npm start script, which writes to ./dist/modernizr-build.js.
./bin/modernizr
npm startThe practical point is that Modernizr is not a drop-in script tag in this workflow. You choose the tests, generate a file, and ship that file. A build that includes every detect in config-all.json is much larger than one that includes the three tests your layout actually branches on.
Build tool integration and the async callback in practice
The README documents integration with Webpack and with Gulp. For Webpack, you install the package, create a modernizr-config.js at the project root that exports an object with a feature-detects array, and add a ModernizrWebpackPlugin instance to the plugins array in webpack.config.js, passing the same feature-detects list. Then you run the normal build script.
const ModernizrWebpackPlugin = require('modernizr-webpack-plugin');
module.exports = {
plugins: [
new ModernizrWebpackPlugin({
"feature-detects": [
"test/feature1",
"test/feature2"
]
})
]
};The plugin name and the config shape come from the README, which uses placeholder test names in its example. Treat those names as placeholders and replace them with real detect paths from the repository. For runtime branching on an async test, the documented pattern is a single callback that receives a boolean.
Modernizr.on("testname", function (result) {
if (result) {
console.log("The test passed!");
} else {
console.log("The test failed!");
}
});Because the callback fires once per on call, code that needs the result in several places should store it rather than register repeatedly.
Where Modernizr gets in the way
The v4 line is an alpha, and the package.json in the repository declares version 4.0.0-alpha while the most recent published release is v3.13.1. That gap matters if you depend on stability. The v4 breaking changes are substantial: support for Node versions 10 and below is dropped and Node 12 or higher is required; the class test is renamed to es6class; many tests move into subdirectories such as storage, audio, battery, canvas, event, image, input, svg and webgl; and several tests are removed entirely, including touchevents, unicode, templatestrings, contains and datalistelem. Any build configuration that names a moved or removed test will not behave the way it did before the upgrade. There is a second limitation that is easy to miss: Modernizr tells you whether a feature exists, not whether it works well on the current device. A browser can report support for a feature and still deliver poor results, and detection cannot see that. Finally, if your project already targets a single modern engine, every byte of the generated file is overhead you do not need.
Modernizr against a plain CSS feature query
The obvious alternative for style-only branching is @supports, which asks the browser directly about a CSS declaration and needs no build step and no JavaScript. The difference in approach is where the decision lives. @supports answers a CSS question inside CSS, and it cannot help when the branch is in JavaScript or when the thing you are testing is not expressible as a declaration. Modernizr answers a JavaScript question and then republishes the answer as a class, so the same fact is usable from both sides. If you only ever need to change a colour or a layout when a property is unsupported, @supports is the smaller tool. If you need the result inside a script, or you need to test something that is not a CSS property, Modernizr covers ground @supports does not.
Maintenance, licence and the cost of upgrading
The repository is not archived, and the last push was on 2026-09-09. The published release history shows v3.13.1 and v3.13.0 on 2024-08-06, and v3.12.0 on 2022-02-15. So the stable line has moved slowly while the default branch carries an alpha version. The licence is MIT, which is permissive and places few obligations on how you redistribute the generated file; this is a description of the licence text, not legal advice. The upgrade cost is concentrated in test names. Because v4 renames class to es6class, moves a long list of detects into subdirectories, and deletes touchevents, unicode, templatestrings, contains and datalistelem, a configuration written against v3 needs to be audited test by test before it will build the same bundle under v4. The project also requires Node 12 or higher from v4 onward, which can force a toolchain bump in older repositories. Testing is documented as npm test for the console via mocha-headless-chrome, with npm run serve-gh-pages plus the unit and integration pages on localhost:8080 for browser runs.
Editorial conclusion
Adopt Modernizr if you still support browsers whose feature set you cannot assume, and you want detection results as both a global object and html classes. Do not adopt it if your build already targets a single modern engine, because the generated file is dead weight. Before committing, run a build with only the tests you need, confirm the resulting file exposes what your code reads, and check that the v4 test renames and removals do not break any test name you already reference.
Frequently asked questions
What is Modernizr and what is it used for?
It is a JavaScript library that detects HTML5 and CSS3 features in the current browser. It publishes the results as properties on a global Modernizr object and as classes on the html element, so CSS and JavaScript can branch on the same detection.
How do I use Modernizr in a project?
Install it from npm, build a version that includes the feature detects you need, and ship the generated file. At runtime you read Modernizr properties, the html classes, or register a callback with Modernizr.on for asynchronous tests.
What is the latest version of Modernizr?
The most recent published release listed in the repository is v3.13.1 from 2024-08-06. The package.json on the default branch declares 4.0.0-alpha, so the next major line exists in the repository but is not the published stable release.
Is Modernizr still needed?
It is needed when you cannot assume a feature exists in the browsers you support, because it reports support at runtime instead of guessing from the user agent. If your project targets a single modern engine, the generated file adds weight without changing behaviour.
What can I use instead of Modernizr for CSS features?
CSS @supports asks the browser about a declaration directly and needs no build step. The difference is reach: @supports answers a CSS question inside CSS, while Modernizr also makes the answer available to JavaScript.
Why do I get "modernizr is not defined"?
That error means the Modernizr file is not loaded before the code that reads the global object. The README's workflow generates a build rather than shipping a fixed script, so check that the generated file is included ahead of your own script.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/modernizr-modernizr)