Materialize CSS 1.x components stay static without explicit M.init
Materialize CSS 1.x styles components with CSS but requires explicit JavaScript initialization via M.Component.init. Learn the correct init timing, options pattern, and how to avoid duplicate listeners and version mismatches.
20 Dec 2025, 10:41 UTC

Sidenav is styled but never opens
Materialize CSS 1.x gives you the look for free. The behavior does not. A sidenav, tabs, modal or collapsible will render with correct classes and layout, but it will not respond to clicks until you explicitly initialize the JavaScript component. The useful takeaway is: CSS is static, JS is opt-in. Init once after the DOM exists, pass options as a plain object, and keep a reference to the returned instance for programmatic control.
How initialization works in 1.x
Materialize 1.x removed jQuery. All interactive widgets live under the global namespace M. Each widget exposes a static init method that accepts a NodeList or single element and an options object. The call returns an array of instances, each with methods like open, close and destroy.
Initialization must happen after the elements are in the DOM. The common pattern is to run the init inside DOMContentLoaded or after the module that injects markup has finished. Responsive utility classes such as hide-on-med-and-up are pure CSS media queries. They change visibility, but they do not require re-initialization. The same JS instance continues to work across breakpoints.
A worked sidenav configuration
The following markup is the expected structure for a right-edge sidenav with a trigger. The trigger uses data-target to reference the sidenav id. The CSS alone will position the panel off-screen. The JS makes the trigger functional.
<!-- Trigger -->
<a href="#" data-target="right-nav" class="sidenav-trigger">Menu</a>
<!-- Sidenav -->
<ul id="right-nav" class="sidenav">
<li><a href="#">Item 1</a></li>
<li><a href="#">Item 2</a></li>
</ul>
Initialize in the browser console or in a script loaded after Materialize JS. Run as the page owner in the browser context; no special permissions are required.
document.addEventListener('DOMContentLoaded', () => {
const elems = document.querySelectorAll('.sidenav');
const options = {
edge: 'right',
draggable: false
};
const instances = M.Sidenav.init(elems, options);
});
edge: 'right' places the panel on the right side instead of the default left. draggable: false disables swipe to open on touch devices. Options are applied at init time only; changing the object later does not retroactively update an existing instance.
After init you can control the component without re-querying the DOM:
const el = document.getElementById('right-nav');
const instance = M.Sidenav.getInstance(el);
// instance.open() and instance.close() are available on the instance
A practical way to check the result is to open the browser console on the page, run M.Sidenav.getInstance(document.getElementById('right-nav')), and confirm an object is returned with the expected methods and the options you passed. Resize the viewport and verify the trigger still toggles the same instance.
Limits and common mistakes
Version mismatch
Materialize 0.x used jQuery plugin syntax such as $('.sidenav').sidenav(). In 1.x that API no longer exists and will throw a TypeError. Verify you are loading Materialize 1.x CSS and JS and using the M.Component namespace.
Init timing
Calling M.Sidenav.init before the elements exist yields an empty NodeList. The call succeeds silently and the component stays inert. Always initialize after DOMContentLoaded or after dynamic markup insertion.
Duplicate initialization
Re-initializing the same element creates duplicate event listeners. The panel may open then immediately close or toggle erratically. Check for an existing instance with M.Sidenav.getInstance(el) before calling init again, or destroy the prior instance with instance.destroy() before re-init.
Markup contract
Init succeeds even if the trigger markup is wrong. If data-target does not match the sidenav id, the component initializes but the trigger does nothing. The same applies to tabs requiring matching href and tab id, and modals requiring a trigger with modal-trigger and data-target.
Limitations to keep in mind: options are immutable after init, you must manage instance lifecycle yourself when removing elements from the DOM, and CSS-only visibility changes do not recreate instances. For dynamically added widgets, initialize the new nodes only, not the whole page.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.