This guide covers Materialize from the perspective of architects, build engineers, and contributors: how the framework is structured, how it is compiled, how to customize it through Sass, what its security posture is, and how to extend or contribute to it. It complements the User Guide, which covers day-to-day usage of components and the grid.
All facts here are derived from the framework's source at version 1.0.0.
1. Executive Summary
Materialize is a responsive front-end CSS/JavaScript framework that implements Google's Material Design specification. It is consumed by web developers as two compiled artifacts — materialize.css and materialize.js — produced from Sass and ES2015 JavaScript sources by a Grunt build pipeline.
| Attribute | Value |
|---|---|
| Type | Front-end CSS/JS framework |
| Version | 1.0.0 |
| License | MIT |
| npm package | materialize-css |
| Bower / Meteor package | materialize / materialize:materialize |
| Runtime dependencies | None (cash + anime.js are bundled) |
| Build system | Grunt (Sass, Babel, PostCSS, UglifyJS, Jade) |
| Test framework | Jasmine + jasmine-jquery (PhantomJS) |
| Distribution entry | dist/js/materialize.js, dist/css/materialize.css |
| Sass entry | sass/materialize.scss |
Important
The
Dogfalo/materializerepository is no longer actively maintained (issues #6615, #6691) and has unresolved security advisories (issue #6331: CVE-2019-11002, CVE-2019-11003, CVE-2019-11004). Factor this into adoption and patching decisions — see Section 6.
2. Architecture
2.1 High-level structure
Materialize is two parallel subsystems — a style layer (Sass → CSS) and a behavior layer (ES2015 → bundled JS) — plus a documentation site and starter templates.
+-----------------------------------------------------------+
| Materialize |
| |
| Style layer (Sass) Behavior layer (JS) |
| +------------------------+ +----------------------+ |
| | sass/materialize.scss | | js/component.js | |
| | @import components/* | | js/global.js (M.*) | |
| | - variables | | js/<component>.js x23| |
| | - grid, typography | | js/cash.js (DOM lib) | |
| | - 30+ components | | js/anime.min.js | |
| +-----------+------------+ +-----------+----------+ |
| | grunt sass | grunt concat |
| v v + babel + uglify|
| dist/css/materialize.css dist/js/materialize.js |
+-----------------------------------------------------------+
^ ^
| <link> | <script>
| |
+-----+----------------------------------+-----+
| Consumer web page (your HTML) |
+---------------------------------------------+
Supporting (not shipped to consumers):
jade/ -> documentation site source (compiled to HTML)
templates/ -> downloadable starter templates
tests/spec/ -> Jasmine test specs
extras/ -> vendored noUiSlider
2.2 The JavaScript component model
Every interactive component follows the same pattern, anchored by a shared base class.
Base class — js/component.js:
class Component {
constructor(classDef, el, options) {
if (!(el instanceof Element)) {
console.error(Error(el + ' is not an HTML Element'));
}
// Destroy a prior instance on the same element, then re-init
let ins = classDef.getInstance(el);
if (!!ins) { ins.destroy(); }
this.el = el;
this.$el = cash(el);
}
static init(classDef, els, options) {
// Accepts an Element, NodeList, or jQuery/cash collection
// Returns one instance or an array of instances
}
}
Each component (e.g. js/modal.js) then:
- Declares a private
_defaultsoptions object. - Extends
Componentand stores its instance on the element asel.M_<Name>(e.g.el.M_Modal). - Implements
static get defaults(),static init(els, options), andstatic getInstance(el). - Implements
destroy()plus component-specific public methods (open,close, etc.). - Registers itself on the global
Mnamespace:M.Modal = Modal. - If jQuery is present, registers a jQuery plugin wrapper.
Note
Re-initializing a component on an element that already has an instance automatically destroys the old instance first (handled in the base constructor). This makes
init()idempotent and safe to call after dynamic DOM updates.
2.3 The M global namespace — js/global.js
global.js creates the window.M object (or M for Meteor packages) and attaches framework-wide utilities:
| Member | Purpose |
|---|---|
M.version |
'1.0.0' |
M.AutoInit(context?) |
Initialize all components found in context (default document.body) |
M.initializeJqueryWrapper(plugin, name, classRef) |
Register a jQuery plugin for a component |
M.keys |
Key code map (TAB, ENTER, ESC, ARROW_UP, ARROW_DOWN) |
M.tabPressed / M.keyDown |
Global keyboard-focus state (drives .keyboard-focused on <body>) |
M.guid() |
Generate a unique id |
M.throttle(fn, wait, options) |
Underscore-style throttle |
M.escapeHash(hash) |
Escape special chars in a hash selector |
M.getIdFromTrigger(trigger) |
Resolve a target id from data-target or href |
M.getDocumentScrollTop() / M.getDocumentScrollLeft() |
Cross-browser scroll position |
M.getClosestAncestor(el, condition) |
Walk up the DOM for a matching ancestor |
M.checkWithinContainer(...) / M.checkPossibleAlignments(...) |
Positioning math used by dropdowns/tooltips |
M.getOverflowParent(el) |
Find the nearest non-visible overflow ancestor |
M.elementOrParentIsFixed(el) |
Detect fixed positioning in the ancestor chain |
The module also defines AMD and CommonJS exports so Materialize can be imported as a module (exports.default = M).
2.4 Auto-initialization
M.AutoInit() maps each component class to a CSS selector and calls init() on every match that does not carry the no-autoinit class:
let registry = {
Autocomplete: root.querySelectorAll('.autocomplete:not(.no-autoinit)'),
Carousel: root.querySelectorAll('.carousel:not(.no-autoinit)'),
// … 15 more …
FormSelect: root.querySelectorAll('select:not(.no-autoinit)'),
FloatingActionButton: root.querySelectorAll('.fixed-action-btn:not(.no-autoinit)')
};
for (let pluginName in registry) {
M[pluginName].init(registry[pluginName]);
}
Because auto-init always uses defaults, components needing custom options should be excluded with no-autoinit and initialized manually. (The full selector table is in the User Guide, Section 6.)
2.5 The jQuery wrapper
M.initializeJqueryWrapper generates a jQuery plugin per component:
jQuery.fn[pluginName] = function(methodOrOptions) {
if (plugin.prototype[methodOrOptions]) {
// method call: getters (name starts with "get") return a value;
// other methods run on each matched element
} else if (typeof methodOrOptions === 'object' || !methodOrOptions) {
plugin.init(this, arguments[0]); // initialization
return this;
}
jQuery.error(`Method ${methodOrOptions} does not exist on jQuery.${pluginName}`);
};
This is only invoked when M.jQueryLoaded is true (!!window.jQuery at load time), so jQuery remains an optional dependency.
2.6 Component inventory
23 JavaScript classes back the interactive components (all extend Component except Toast, which is standalone):
| Class | Source | Trigger selector |
|---|---|---|
Autocomplete |
js/autocomplete.js |
.autocomplete |
FloatingActionButton |
js/buttons.js |
.fixed-action-btn |
Carousel |
js/carousel.js |
.carousel |
CharacterCounter |
js/characterCounter.js |
[data-length] |
Chips |
js/chips.js |
.chips |
Collapsible |
js/collapsible.js |
.collapsible |
Datepicker |
js/datepicker.js |
.datepicker |
Dropdown |
js/dropdown.js |
.dropdown-trigger |
Materialbox |
js/materialbox.js |
.materialboxed |
Modal |
js/modal.js |
.modal |
Parallax |
js/parallax.js |
.parallax |
Pushpin |
js/pushpin.js |
.pushpin |
Range |
js/range.js |
input[type=range] |
ScrollSpy |
js/scrollspy.js |
.scrollspy |
FormSelect |
js/select.js |
select |
Sidenav |
js/sidenav.js |
.sidenav |
Slider |
js/slider.js |
.slider |
Tabs |
js/tabs.js |
.tabs |
TapTarget |
js/tapTarget.js |
.tap-target |
Timepicker |
js/timepicker.js |
.timepicker |
Toast |
js/toasts.js |
(programmatic via M.toast) |
Tooltip |
js/tooltip.js |
.tooltipped |
Component |
js/component.js |
(base class) |
Plus non-class behavior modules: js/cards.js (delegated click handler for card reveal), js/forms.js (input/textarea/select enhancements and M.updateTextFields/M.validate_field/M.textareaAutoResize), and js/waves.js (ripple effect).
2.7 Bundled libraries
| Library | File | Role |
|---|---|---|
| cash | js/cash.js |
Lightweight jQuery-compatible DOM API ($) used internally by all components |
| anime.js | js/anime.min.js |
Animation engine, exposed as M.anime |
| noUiSlider | extras/noUiSlider/ |
Optional standalone range slider, shipped separately under extras/ (not part of the core bundle) |
Note
All components are wrapped in IIFEs receiving
cashas$(e.g.(function($, anim){ … })(cash, M.anime)). This means component internals never depend on a global jQuery — only the optional public wrapper does.
3. Requirements
Runtime (consumers)
- A supported browser: Chrome 35+, Firefox 31+, Safari 9+, Opera, Edge, IE 11+.
- The Material Icons web font (linked from Google Fonts) for icon glyphs.
- No JavaScript runtime dependency — jQuery is optional.
Build / development (contributors)
- Node.js ≥ 6 (declared as
"engine": "node >= 6"inpackage.json). - npm (to install dev dependencies).
- Grunt (installed via dev dependencies; run through
npx/localnode_modules).
The build chain pulls in (dev dependencies): grunt and its plugins (grunt-sass, grunt-babel, grunt-postcss, grunt-contrib-concat, grunt-contrib-uglify, grunt-contrib-jade, grunt-contrib-jasmine, grunt-contrib-watch, grunt-browser-sync, grunt-contrib-compress, grunt-banner, and more), plus node-sass, autoprefixer, babel-preset-es2015, jasmine, jasmine-jquery, jquery, phantomjs-prebuilt, prettier, husky, and lint-staged.
4. Build & Customization (Sass)
4.1 Sass entry point
sass/materialize.scss imports every component partial in dependency order:
@charset "UTF-8";
// Color
@import "components/color-variables";
@import "components/color-classes";
// Variables
@import "components/variables";
// Reset
@import "components/normalize";
// components
@import "components/global";
@import "components/badges";
@import "components/icons-material-design";
@import "components/grid";
@import "components/navbar";
@import "components/typography";
// … cards, toast, tabs, tooltip, buttons, dropdown, waves, modal,
// collapsible, chips, materialbox, forms/forms, table_of_contents,
// sidenav, preloader, slider, carousel, tapTarget, pulse,
// datepicker, timepicker
To exclude components you don't use, comment out their @import lines and recompile. This is the supported way to slim down the CSS bundle.
4.2 Customizing via variables
All design tokens live in sass/components/_variables.scss and are declared with !default, so you can override any of them before importing Materialize:
// my-theme.scss
$primary-color: color("blue", "darken-2");
$secondary-color: color("orange", "accent-3");
$num-cols: 12; // grid columns
$gutter-width: 1.5rem; // grid gutter
@import "materialize";
Key variable groups (24 sections in _variables.scss):
| Group | Example variables |
|---|---|
| Colors | $primary-color, $secondary-color, $success-color, $error-color, $link-color |
| Grid | $num-cols (12), $gutter-width (1.5rem) |
| Breakpoints | $small-screen-up (601px), $medium-screen-up (993px), $large-screen-up (1201px) and matching media-query strings ($medium-and-up, etc.) |
| Buttons | $button-height (36px), $button-raised-background, $button-floating-size (40px), $button-radius (2px) |
| Typography | $font-stack (system font stack), $h1-fontsize (4.2rem) … $h6-fontsize (1.15rem), $off-black |
| Forms | $input-height (3rem), $input-border-color, $input-focus-color, $radio-fill-color, $switch-bg-color |
| Navbar / Sidenav | $navbar-height (64px / 56px mobile), $sidenav-width (300px) |
| Components | per-component colors and sizes for cards, carousel, collapsible, chips, datepicker, dropdown, toasts, tabs, tables, collections, progress bar |
Tip
Because every variable is
!default, the safest customization pattern is a thin wrapper SCSS file that sets overrides first and then@import "materialize";. Never edit the framework partials directly — your changes would be lost on upgrade.
4.3 Responsive breakpoints
The breakpoint variables drive both the grid and the responsive helper classes:
| Variable | Value |
|---|---|
$small-screen-up |
601px |
$medium-screen-up |
993px |
$large-screen-up |
1201px |
$small-screen |
600px |
$medium-screen |
992px |
$large-screen |
1200px |
5. The Grunt Build Pipeline
The build is defined in Gruntfile.js. npm scripts (package.json) provide the common entry points:
| npm script | Command | Purpose |
|---|---|---|
dev |
grunt monitor |
Watch + live-reload development server |
test |
grunt travis |
Full CI build + tests |
precommit |
lint-staged |
Run Prettier on staged JS via Husky pre-commit hook |
5.1 Registered Grunt tasks
| Task | Steps |
|---|---|
grunt monitor |
concurrent:monitor — runs watch + BrowserSync together (the dev loop) |
grunt server |
browserSync + notify — serves the docs at http://localhost:8000 |
grunt js_compile |
concat:temp → configureBabel → babel:bin → clean:temp |
grunt sass_compile |
sass:gh → sass:bin → postcss:gh → postcss:bin → notify |
grunt jade_compile |
jade (compile docs templates) → notify |
grunt travis |
js_compile + sass_compile + jasmine (the CI target) |
grunt release |
Full production build (see below) |
5.2 The release task
grunt release produces the distributable artifacts:
sass:expanded -> sass:min (compile CSS, expanded + minified)
postcss:expanded / postcss:min (autoprefix both)
concat:dist (concatenate JS in fixed order)
babel:dist (transpile ES2015 -> ES5)
uglify:dist / uglify:extras (minify core + extras)
usebanner:release (prepend license banner)
compress:main / compress:src (zip standard + source bundles)
compress:starter_template / compress:parallax_template
replace:version / replace:readme (stamp version numbers)
rename:rename_src / rename:rename_compiled
clean:temp
5.3 JavaScript concatenation order
concat assembles the bundle in a fixed order (dependencies first):
js/cash.js (DOM library)
js/component.js (base class)
js/global.js (M namespace)
js/anime.min.js (animation engine)
js/collapsible.js, dropdown.js, modal.js, materialbox.js, parallax.js,
tabs.js, tooltip.js, waves.js, toasts.js, sidenav.js, scrollspy.js,
autocomplete.js, forms.js, slider.js, cards.js, chips.js, pushpin.js,
buttons.js, datepicker.js, timepicker.js, characterCounter.js,
carousel.js, tapTarget.js, select.js, range.js
Important
cash.js,component.js, andglobal.jsmust be concatenated before any component. Components referencecash($), theComponentbase class, and theMnamespace at definition time. Re-ordering breaks the bundle.
5.4 CSS toolchain
- Sass compiled by
node-sass(viagrunt-sass), output inexpandedandcompressedstyles. - Autoprefixer (PostCSS) targets:
last 2 versions,Chrome >= 30,Firefox >= 30,ie >= 10,Safari >= 8.
5.5 JavaScript toolchain
- Babel with
babel-preset-es2015(arrow functions, block scoping, classes, template literals, object super) transpiles ES2015 → ES5. - UglifyJS minifies the result.
- Source maps are produced for the
binbuild.
5.6 Documentation site
The documentation site lives in jade/ (Jade/Pug templates) and is compiled to HTML by grunt-contrib-jade. To run the docs locally:
git clone https://github.com/Dogfalo/materialize
cd materialize
npm install
grunt monitor
Then open http://localhost:8000 — BrowserSync serves the compiled docs and
live-reloads on source changes.
6. Security Analysis
Caution
Materialize at this version has known, unresolved security advisories and is unmaintained. Treat it accordingly in any environment that processes untrusted input.
6.1 Known advisories
Issue #6331 (34 comments) tracks three CVEs that remain open:
| Advisory | Notes |
|---|---|
| CVE-2019-11002 | Reported against Materialize; unresolved upstream |
| CVE-2019-11003 | Reported against Materialize; unresolved upstream |
| CVE-2019-11004 | Reported against Materialize; unresolved upstream |
These relate to cross-site scripting vectors in component handling. Because the project is no longer maintained, no upstream patch is expected.
Warning
Specific exploit details are intentionally not reproduced here. Consult the official CVE database entries and the upstream issue for authoritative guidance before deploying Materialize in security-sensitive contexts.
6.2 Inherent XSS surface
Several components accept HTML strings as options or attributes and inject them into the DOM without sanitization:
M.toast({ html: ... })— toast content.- Tooltip
htmloption /data-tooltipattribute. - Autocomplete suggestion rendering and Chips tags.
If any of this content can originate from users, sanitize it on your side before passing it to Materialize. The framework does no escaping.
6.3 No server-side surface
Materialize is entirely client-side. It performs no network requests, defines no API endpoints, reads no secrets, and has no authentication logic. Its security profile is limited to DOM/XSS concerns in the browser.
6.4 Mitigations
- [ ] Sanitize any user-derived content before passing it to
htmloptions. - [ ] Pin to a specific version and review the CVE advisories for applicability.
- [ ] Consider a maintained fork or an actively maintained alternative for new long-lived projects.
- [ ] Apply a Content Security Policy to limit the blast radius of any injection.
7. Code Quality & Conventions
| Aspect | Detail |
|---|---|
| Language level | ES2015 (classes, arrow functions, template literals), transpiled to ES5 for distribution |
| DOM access | Internal cash library, never a hard jQuery dependency |
| Formatting | Prettier, enforced on staged js/*.js via lint-staged + Husky precommit hook |
| Linting | .eslintrc present at repo root |
| Editor config | .editorconfig, .prettierrc, .prettierignore at root |
| Component consistency | Every component implements the same static surface (init, getInstance, defaults, destroy) — highly uniform, easy to reason about |
| Instance storage | Each instance is stored on its element as el.M_<Name>, enabling getInstance() lookups without a registry |
| Memory hygiene | Components implement destroy() to remove event listeners and DOM additions; the base constructor auto-destroys a prior instance before re-init |
Note
The codebase predates ES modules — it relies on concatenation order and a shared global (
M) rather thanimport/exportbetween files. This is the main structural constraint a modernization effort would need to address.
8. Testing
| Aspect | Detail |
|---|---|
| Framework | Jasmine (grunt-contrib-jasmine) |
| DOM assertions | jasmine-jquery |
| Browser | PhantomJS (phantomjs-prebuilt) |
| Specs | tests/spec/**/*Spec.js |
| Helper | tests/spec/helper.js |
| Tested under | the compiled bin/materialize.js + bin/materialize.css |
| Run | grunt travis (or grunt jasmine directly after a build) |
Specs exist for: autocomplete, cards, carousel, chips, collapsible, dropdown, fab, materialbox, modal, select, sidenav, tabs, toast, and tooltip. Note that not all 23 components have spec coverage — date/time pickers, slider, scrollspy, pushpin, parallax, range, and character counter are not covered by Jasmine specs.
There is also a manual test/html/ directory with standalone HTML pages for visually exercising components (badges, buttons, cards, carousel, chips, collapsible, dropdown, fixed_navbar, forms, materialbox, multiple_modals, multiple_sidenav, overlayZindex, pushpin, scrollfire, scrollspy, tabs, waves).
9. Operations: Deploying Materialize
Materialize ships as static assets, so "deployment" means hosting two files (plus the icon font).
Production checklist
- [ ] Use minified assets:
materialize.min.css,materialize.min.js. - [ ] Link the Material Icons font (self-host it if you cannot depend on Google Fonts).
- [ ] Serve assets over HTTPS with long-lived cache headers (they are versioned/immutable per release).
- [ ] Pin the exact version (CDN URL or
package.jsonversion) rather than a floating tag. - [ ] Initialize components after
DOMContentLoaded(or via a bundler/framework lifecycle hook). - [ ] If using a bundler, import from
dist/js/materialize.js(themainfield) orsass/materialize.scss(thesassfield) to compile only what you need.
Bundler integration
The npm package declares module entry points:
{
"main": "dist/js/materialize.js",
"style": "dist/css/materialize.css",
"sass": "sass/materialize.scss"
}
With a Sass-aware bundler you can @import the Sass entry (or individual component partials) and override variables, getting tree-trimmed CSS for your component subset.
10. Improvements & Technical Debt
Observations drawn from the source and the community issue tracker:
| Area | Observation | Suggested direction |
|---|---|---|
| Maintenance | Project is unmaintained (#6615, #6691); open CVEs (#6331) | Migrate to a maintained fork/alternative for new projects, or fork and patch |
| Modularity | No way to install/include a single component from the standard build (#6422) | The Sass @import exclusion approach partially solves CSS; JS would need an ES-module refactor |
| Module system | Files rely on concat order + global M rather than ES imports |
Refactor to ES modules with explicit imports; would also enable real tree-shaking |
| XSS hardening | html options inject unsanitized markup |
Add optional sanitization or document the risk prominently in component APIs |
| Test coverage | ~14 of 23 components have Jasmine specs | Add specs for datepicker, timepicker, slider, scrollspy, pushpin, parallax, range, characterCounter |
| Select reliability | Multiple open issues on the custom Select across iOS/frameworks (#6647, #6698, #4529, #1861) | Harden custom select or improve browser-default fallback guidance |
| Build tooling | Grunt + PhantomJS are themselves aging/deprecated | Modernize to a current bundler and a headless-Chrome test runner |
| Datepicker bug | Wrong date when setDate runs pre-init (#6011) |
Guard setDate against being called before initialization completes |
11. Quick Reference
Global API (M)
| Call | Effect |
|---|---|
M.AutoInit() |
Initialize all components on the page |
M.<Component>.init(els, opts) |
Initialize one/many elements |
M.<Component>.getInstance(el) |
Retrieve an instance |
M.toast({ html }) |
Show a toast |
M.Toast.dismissAll() |
Dismiss all toasts |
M.updateTextFields() |
Re-float labels after setting input values |
M.textareaAutoResize($el) |
Resize a textarea to fit content |
M.validate_field($el) |
Apply validation styling |
M.version |
Framework version string ('1.0.0') |
Build commands
| Command | Purpose |
|---|---|
npm install |
Install dev dependencies |
grunt monitor |
Dev watch + live reload (docs at localhost:8000) |
grunt travis |
CI build + Jasmine tests |
grunt release |
Full production build (dist + zips) |
grunt js_compile |
Concatenate + Babel-transpile JS only |
grunt sass_compile |
Compile + autoprefix CSS only |
Key files
| Path | Role |
|---|---|
sass/materialize.scss |
Sass entry point (component imports) |
sass/components/_variables.scss |
All design tokens (!default) |
js/component.js |
Base Component class |
js/global.js |
M namespace + auto-init + jQuery wrapper |
Gruntfile.js |
Build pipeline definition |
package.json |
Package metadata, scripts, dev dependencies |
dist/ |
Compiled CSS/JS (release output) |
extras/noUiSlider/ |
Optional standalone range slider |