Skip to content

Magento CSS and JavaScript — RequireJS, Less and Grunt

DodaTech Updated 2026-06-27 13 min read

In this tutorial, you'll learn how Magento's CSS and JavaScript system works with Less compilation, RequireJS module loading, JavaScript mixins, and static content deployment for production.

What You'll Learn

  • How Less CSS compilation works with Grunt and server-side tools
  • How RequireJS AMD modules load JavaScript dependencies
  • How to create JavaScript mixins to extend existing modules
  • How jQuery UI widgets and Knockout JS power the frontend
  • How to deploy static content for production environments

Why It Matters

Magento's frontend asset pipeline is complex. CSS involves Less compilation with multiple source files per page. JavaScript uses the AMD (Asynchronous Module Definition) pattern through RequireJS. If you do not understand this system, you will struggle to add custom styles or JavaScript to your store. Worse, deploying without running the correct static content commands will result in a broken storefront. This knowledge separates professional Magento developers from beginners who only know how to edit files in the admin panel.

Real-World Use

A marketing campaign requires a custom popup on the checkout page that shows a discount code when the user spends over $100. The popup needs smooth animations (jQuery), the discount logic needs to call an API endpoint (RequireJS), and the styling must match the brand colors (Less variables). You create a Less file that imports the brand colors from _variables.less, write a RequireJS module that loads the popup widget, and deploy everything using setup:static-content:deploy. The whole feature takes a few hours instead of weeks.

Learning Path

flowchart LR
  A["22: Layout XML"] --> B["23: PHTML Templates"]
  B --> C["24: CSS and JavaScript
You are here"]:::current classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

Less CSS Compilation

Magento uses Less (Leaner CSS) as its Css Preprocessor. All core styles are written in Less files that compile into regular CSS. There are two compilation methods: client-side (Grunt) and server-side (PHP Less compiler).

Less File Structure

app/design/frontend/Vendor/Theme/web/css/
  - styles-l.less       # Desktop styles
  - styles-m.less       # Mobile styles
  - print.less          # Print styles
  - _theme.less         # Your theme's custom variables and overrides
  - _extend.less        # Extend or override core Less styles
  - _variables.less     # Color palette, typography, spacing variables

The underscore prefix files are partials — they are imported by other Less files and do not compile to standalone CSS.

styles-l.less and styles-m.less

Magento generates two CSS files per page: one for desktop (styles-l) and one for mobile (styles-m). These are the entry points that import all other Less files.

The styles-l.less file typically looks like:

@import '_variables.less';
@import '_theme.less';
@import '_extend.less';
@import '../Magento_Catalog/css/module.less';

Variables, Theme, and Extend Files

_variables.less — Define your brand colors, fonts, and spacing:

@color-brand-primary: #1a73e8;
@color-brand-secondary: #ff6f00;
@font-family-base: 'Open Sans', sans-serif;
@spacing-unit: 8px;

_theme.less — Override specific Magento Less variables:

@primary__color: @color-brand-primary;
@button-primary__background: @color-brand-primary;
@link__color: @color-brand-primary;

_extend.less — Add custom styles or override component styles:

.product-item {
    border: 1px solid #eee;
    padding: @spacing-unit * 2;
}

Grunt Setup

Grunt is a JavaScript task runner that compiles Less files, watches for changes, and reloads the browser automatically.

Installing and Configuring Grunt

  1. Install Node.js dependencies (Magento ships with package.json in the root):
cd /var/www/magento
npm install
  1. Configure your theme in dev/tools/grunt/configs/themes.js:
module.exports = {
    your_theme: {
        area: 'frontend',
        name: 'Vendor/Theme',
        locale: 'en_US',
        files: [
            'css/styles-m',
            'css/styles-l',
            'css/print'
        ],
        dsl: 'less'
    }
};
  1. Run Grunt commands:
# Compile Less files for your theme
grunt exec:your_theme

# Watch for changes and recompile automatically
grunt watch:your_theme

Grunt Less Workflow

The grunt exec command processes all Less files specified in the theme configuration. The output goes to pub/static/frontend/Vendor/Theme/en_US/css/. When you make a Less change, you run grunt exec:your_theme to recompile, or use grunt watch for automatic recompilation.

grunt clean

To remove generated static files before a fresh compilation:

grunt clean:your_theme

Then run grunt exec again.

Server-Side Less Compilation

The server-side compiler uses Magento's PHP Less processor. It is slower than Grunt but does not require Node.js. It is the standard approach for production deployments.

bin/magento setup:static-content:deploy -f

The -f flag forces a re-deployment even if files already exist. You can specify locales:

bin/magento setup:static-content:deploy en_US en_GB de_DE -f

This command compiles all Less files, generates all CSS, copies all JavaScript, and places everything in pub/static/frontend/ with versioning.

When to Use Grunt vs Server-Side

Scenario Tool
Active theme development Grunt (fast, watch mode, browser sync)
Finalizing a design change Grunt (verify compiled output)
Production deployment setup:static-content:deploy
CI/CD pipeline setup:static-content:deploy
Quick CSS fix Server-side (no Node.js needed)

RequireJS and AMD Modules

Magento uses RequireJS to implement the AMD module pattern. AMD stands for Asynchronous Module Definition. It means JavaScript modules are loaded on demand, not all at once. This improves page load time because the browser only loads the JavaScript it needs.

How RequireJS Works

Instead of using multiple <script> tags, Magento loads one RequireJS file, and then all other JavaScript is defined as modules that RequireJS loads as needed.

A simple AMD module looks like:

define(['jquery', 'mage/translate'], function ($, $t) {
    'use strict';

    return function (config, element) {
        $(element).on('click', function () {
            alert($t('Button clicked!'));
        });
    };
});

The first argument is an array of dependencies (module paths). The second argument is a callback function that receives the loaded modules. The function returns the module's public API.

requirejs-config.js

The requirejs-config.js file configures module paths, shims, and maps. It lives in your theme's web directory:

app/design/frontend/Vendor/Theme/requirejs-config.js:

var config = {
    paths: {
        'custom-module': 'Vendor_Theme/js/custom-module'
    },
    shim: {
        'custom-lib': {
            deps: ['jquery']
        }
    },
    map: {
        '*': {
            'Magento_Checkout/js/view/shipping': 'Vendor_Theme/js/view/shipping-override'
        }
    },
    config: {
        mixin: {
            'Magento_Checkout/js/view/shipping': {
                'Vendor_Theme/js/shipping-mixin': true
            }
        }
    }
};
Configuration Purpose
paths Define module aliases
shim Declare dependencies for non-AMD scripts
map Replace one module with another
config.mixin Add mixins to existing modules

Loading a RequireJS Module in a PHTML Template

To load a RequireJS module from a PHTML template, use the data-mage-init attribute:

<div data-mage-init='{"Vendor_Theme/js/custom-module": {"message": "Hello"}}'>
    Click me
</div>

Magento's data-mage-init attribute automatically initializes the module on page load. It passes the JSON configuration to the module's function as the first argument.

Alternatively, you can use the <script type="text/x-magento-init"> block:

<script type="text/x-magento-init">
{
    ".custom-selector": {
        "Vendor_Theme/js/custom-module": {
            "message": "Hello"
        }
    }
}
</script>

JavaScript Mixins

Mixins allow you to extend existing RequireJS modules without modifying the original file. This is Magento's JavaScript equivalent of PHP plugins.

Creating a Mixin

Create app/design/frontend/Vendor/Theme/Magento_Checkout/web/js/shipping-mixin.js:

define(function () {
    'use strict';

    return function (targetModule) {
        targetModule.originalMethod = targetModule.someMethod;

        targetModule.someMethod = function () {
            // Custom logic before
            var result = targetModule.originalMethod.apply(this, arguments);
            // Custom logic after
            return result;
        };

        return targetModule;
    };
});

Registering the Mixin

In requirejs-config.js:

var config = {
    config: {
        mixin: {
            'Magento_Checkout/js/view/shipping': {
                'Vendor_Theme/js/shipping-mixin': true
            }
        }
    }
};

The mixin receives the original module as the targetModule parameter, adds or overrides methods, and returns the modified module.

jQuery UI Widgets

Magento uses jQuery UI widgets extensively. Many storefront components are jQuery widgets: price slider, gallery navigation, accordion, tabs, and modals.

Declaring a jQuery Widget

define([
    'jquery',
    'jquery-ui-modules/widget'
], function ($) {
    'use strict';

    $.widget('vendor.customWidget', {
        options: {
            message: 'Default message',
            duration: 300
        },

        _create: function () {
            this.element.on('click', $.proxy(this._onClick, this));
        },

        _onClick: function () {
            alert(this.options.message);
        },

        destroy: function () {
            this.element.off('click');
            this._super();
        }
    });

    return $.vendor.customWidget;
});

Using the Widget

<div data-mage-init='{"vendor.customWidget": {"message": "Widget initialized!"}}'>
    Click for a message
</div>

Magento's built-in jQuery widgets include:

Widget Module Purpose
priceSlider Magento_Catalog Category page price filtering
gallery Magento_Catalog Product image gallery
navigation Magento_Theme Top navigation menu
accordion Magento_Theme Collapsible sections
modal Magento_Ui Popup modals and dialogs
tabs Magento_Theme Tabbed content sections

Knockout JS and UI Components

Knockout JS is a JavaScript MVVM framework that Magento uses for complex UI components. The checkout page, customer account sections, and admin panel all use Knockout.

Knockout JS Basics

Knockout uses observables and computed properties to bind data to HTML:

define([
    'ko',
    'uiComponent'
], function (ko, Component) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Vendor_Module/custom-component',
            count: 0
        },

        initObservable: function () {
            this._super();
            this.observe('count');
            return this;
        },

        increment: function () {
            this.count(this.count() + 1);
        }
    });
});

The corresponding Knockout HTML template:

<div>
    <p>Current count: <span data-bind="text: count"></span></p>
    <button data-bind="click: increment">Increment</button>
</div>

UI Components in Layout XML

UI components are declared in layout XML using the jsLayout argument:

<block class="Magento\Framework\View\Element\Template"
       name="custom.component"
       template="Vendor_Module::component.phtml"
       cacheable="false">
    <arguments>
        <argument name="jsLayout" xsi:type="array">
            <item name="components" xsi:type="array">
                <item name="custom-component" xsi:type="array">
                    <item name="component" xsi:type="string">Vendor_Module/js/view/custom-component</item>
                    <item name="config" xsi:type="array">
                        <item name="title" xsi:type="string">My Custom Component</item>
                    </item>
                </item>
            </item>
        </argument>
    </arguments>
</block>

Static Content Deployment

Deploying static content is a critical step when moving from development to production. It compiles Less, minifies JavaScript, and generates versioned files.

The Command

bin/magento setup:static-content:deploy -f

Additional useful flags:

# Deploy for multiple locales
bin/magento setup:static-content:deploy en_US en_GB de_DE -f

# Exclude themes (faster deployment)
bin/magento setup:static-content:deploy -f --exclude-theme=Magento/luma

# Exclude languages
bin/magento setup:static-content:deploy -f --exclude-language=en_GB

# Compact strategy (faster, smaller files)
bin/magento setup:static-content:deploy -f -s compact

What the Command Does

  1. Scans all installed themes and modules
  2. Compiles Less files to CSS
  3. Minifies CSS and JavaScript
  4. Generates RequireJS configuration
  5. Copies all static files to pub/static/frontend/Vendor/Theme/locale/
  6. Appends version signatures to file names for cache busting

Developer Mode vs Production Mode

In developer mode, Magento serves static files directly from the module and theme directories. You can edit CSS and JS and see changes immediately without deployment. In production mode, Magento serves only files from pub/static/, which means you must run setup:static-content:deploy after any change.

# Check current mode
bin/magento deploy:mode:show

# Switch to developer mode (for development)
bin/magento deploy:mode:set developer

# Switch to production mode (for live store)
bin/magento deploy:mode:set production

Common Mistakes

  1. Forgetting to run setup:static-content:deploy after Less changes. In production mode, the storefront loads CSS from pub/static/. If you edit a Less file and do not deploy, the changes do not appear. Always run the deploy command before testing on a production-mode store.

  2. Editing compiled CSS instead of Less files. Beginners sometimes edit the generated CSS files in pub/static/. These files are overwritten on the next deployment. Always edit the .less source files in your theme.

  3. Wrong RequireJS module path. RequireJS paths are case-sensitive and follow the Vendor_Module/js/file convention. A path like Magento_Catalog/js/Product will not load product.js — module names match directory names, and the path must be lowercase after js/.

  4. Not using data-mage-init properly. The JSON value in data-mage-init must be valid JSON. Single quotes around the entire attribute value, but double quotes inside the JSON. A missing comma or a single quote inside the JSON breaks the entire page.

  5. Missing theme in Grunt config. If you add a custom theme but do not register it in dev/tools/grunt/configs/themes.js, Grunt will not compile its Less files. Always add your theme to the Grunt configuration before running grunt exec.

Practice Questions

  1. What is the difference between Grunt and setup:static-content:deploy? Answer: Grunt compiles Less files during development with watch mode and automatic browser reloading. It is fast and ideal for active theme work. The setup:static-content:deploy command compiles all static content for production deployment, including Less compilation, JavaScript Minification, and file versioning. Use Grunt for development and setup:static-content:deploy for production.

  2. How does a JavaScript mixin differ from a map override in RequireJS? Answer: A mixin extends an existing module by wrapping it and adding new functionality while keeping the original methods accessible. A map override replaces the original module entirely with a different implementation. Mixins are preferred when you only need to add or modify specific behavior, while map is used when you need a complete replacement.

  3. What are the two CSS files Magento generates per page and what do they target? Answer: styles-l.less compiles to desktop styles (for viewports wider than the mobile breakpoint). styles-m.less compiles to mobile styles (for viewports at or below the mobile breakpoint). These are defined in the theme configuration and target different device widths using CSS media queries.

  4. Challenge: Create a custom RequireJS module that fetches a JSON endpoint using jQuery's $.ajax, displays the result in a Knockout JS template, and adds a loading spinner during the request. Register it on the checkout page using data-mage-init and verify it works after running setup:static-content:deploy.

FAQ

What is RequireJS and why does Magento use it?

RequireJS is a JavaScript module loader that implements the AMD (Asynchronous Module Definition) specification. Magento uses it to manage JavaScript dependencies and load modules on demand. This improves page load time by loading only the JavaScript needed for the current page, and it prevents conflicts between different modules by isolating them in separate scopes.

{{< faq "How do I add custom JavaScript to a Magento page?" "Create a RequireJS module in your theme's web/js/ directory, then initialize it using the data-mage-init HTML attribute or a <script type=\"text/x-magento-init\"> block in your PHTML template. Register module paths in your theme's requirejs-config.js file if the module is not already mapped." >}}

What is the difference between `_extend.less` and `_theme.less`?

_theme.less is for overriding Magento's Less variables like colors, fonts, and spacing units. _extend.less is for adding custom CSS rules or overriding specific component styles. In practice, _extend.less is used more often because most customizations involve adding new styles rather than changing variables.

Do I need Grunt to develop a Magento theme?

No. You can use the server-side Less compiler by running bin/magento setup:static-content:deploy after each change. However, Grunt provides a much faster workflow with watch mode that recompiles automatically and browser synchronization. For serious theme development, Grunt is strongly recommended.

Why does my JavaScript not work after deploying to production?

The most common cause is that you ran setup:static-content:deploy but did not clear the browser cache, or the static file versioning changed the file URL. Run bin/magento cache:flush after deployment and perform a hard browser refresh (Ctrl+F5). Also check that the file paths in requirejs-config.js match the expected locations in pub/static/.

Mini Project

Your task: Create a custom "Back to Top" button on the product page.

  1. Create a Less file at app/design/frontend/Vendor/Theme/web/css/_extend.less with styles for a fixed-position "Back to Top" button that appears after scrolling 500px.
  2. Create a RequireJS module at Vendor_Theme/web/js/back-to-top.js that uses jQuery to show/hide the button and scroll to the top on click.
  3. Initialize the module in Magento_Catalog/templates/product/view/form.phtml using data-mage-init.
  4. Register the module path in requirejs-config.js.
  5. Run grunt exec to compile Less, verify the button works in developer mode, then run setup:static-content:deploy and verify it works in production mode.
  6. Test the button on mobile and desktop, ensuring it respects the mobile breakpoint.

This exercise covers all three major areas: Less styling, RequireJS module creation, and static content deployment. It is a real feature requested by many stores.

What's Next

Now that you understand Magento's frontend pipeline, it is time to dive into backend module development:

Continue to Lesson 25: Module Structure — Module architecture, registration, and directory structure.

Related lessons:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro