--- name: magento2-frontend-dev description: | This skill should be used when the user asks to "create a Knockout.js component", "add a UI Component", "modify layout XML", "customize a template", "write LESS CSS", "style with the Magento UI library", "add a RequireJS module", "extend JavaScript", "customize checkout", or "modify the cart page". Covers Magento 2 frontend development using the Luma/Blank theme. DEPENDENT on magento2-dev-core for backend patterns and escaping. compatibility: claude, codex, opencode, copilot depends: [magento2-dev-core] metadata: audience: frontend developers workflow: magento --- # Magento 2 Frontend Developer This skill covers Luma/Blank theme development, Knockout.js, RequireJS, LESS CSS, and UI Components. ## Related Skills **REQUIRED BACKGROUND:** Load `magento2-dev-core` first — it defines the escaping (`escapeHtml`/`escapeHtmlAttr`/`escapeJs`) and backend patterns this skill's templates and view models rely on. This skill targets Luma/Blank-derived themes. If the project's `theme.xml` parent is `Hyva/default` or `Hyva/reset` (or `composer.json` requires `hyva-themes/*`), use `magento2-hyva-dev` instead — the two frontend stacks are mutually exclusive and share almost no code patterns. ## Theme Structure ``` app/design/frontend/Vendor/Theme/ ├── registration.php ├── theme.xml ├── composer.json ├── media/ │ └── preview.jpg ├── web/ │ ├── css/ │ │ └── source/ │ │ ├── _extend.less │ │ ├── _theme.less │ │ └── _variables.less │ ├── js/ │ │ └── namespace/ │ │ └── module.js │ └── images/ └── Magento_Theme/ ├── layout/ │ ├── default.xml │ └── default_head_blocks.xml └── templates/ └── header.phtml ``` ## RequireJS Modules ### Creating a Module ```javascript // web/js/namespace/module.js define([ 'jquery', 'ko', 'uiComponent', 'Magento_Customer/js/customer-data' ], function ($, ko, Component, customerData) { 'use strict'; return Component.extend({ defaults: { template: 'Namespace_Module/template-name', exports: { value: '${ $.provider }:data.value' }, tracks: { value: true } }, /** @inheritdoc */ initialize: function () { this._super(); // Initialization logic }, /** @inheritdoc */ initObservable: function () { this._super() .observe('value'); return this; }, /** * Example method * @returns {string} */ getFormattedValue: function () { return this.value() + ' formatted'; } }); }); ``` ### Using a Module in Template ```html
``` ## Knockout.js Patterns ### ViewModel Structure ```javascript define(['ko'], function () { 'use strict'; return function (config, element) { var self = this; // Observable properties self.products = ko.observableArray(config.products || []); self.isLoading = ko.observable(false); self.selectedId = ko.observable(null); // Computed properties self.hasProducts = ko.computed(function () { return self.products().length > 0; }); self.selectedProduct = ko.computed(function () { return self.products().find(function (p) { return p.id === self.selectedId(); }); }); // Methods self.selectProduct = function (product) { self.selectedId(product.id); }; self.loadMore = function () { self.isLoading(true); // AJAX call $.get('/api/products', function (data) { self.products(self.products().concat(data)); self.isLoading(false); }); }; // Initialize self.init = function () { if (config.enableAutoLoad) { self.loadMore(); } }(); }; }); ``` ### Knockout Template ```html
No products available
``` ## Layout XML ### Reference ```xml ``` ## LESS CSS ### Structure ```less // web/css/source/_extend.less // Main entry point for theme customizations // Import lib (Magento UI library) @import 'lib/_lib.less'; // Import vendor styles @import '_components.less'; // Your theme variables @color-primary: #1979c3; @color-secondary: #f0f0f0; // Extend parent theme @import '_theme.less'; // Custom styles .block-product { margin-bottom: @indent__l; &__title { font-size: 20px; color: @color-primary; } &__image { width: 100%; } } ``` ### UI Library Mixins ```less // Using Magento UI library mixins .product-grid { .lib-css(display, flex); .lib-css(flex-wrap, wrap); .lib-css(gap, 20px); .lib-list-reset(); } // Buttons .action.primary { .lib-button-replace(); .lib-button-primary(); } // Forms .field { .lib-form-field(); } // Links a { .lib-link($_linkColor: @color-primary); } ``` ### Responsive Breakpoints ```less // Mobile first approach @mobile: 640px; @tablet: 768px; @desktop: 1024px; .product-card { width: 100%; @media (min-width: @tablet) { width: 50%; } @media (min-width: @desktop) { width: 33.333%; } } ``` ## UI Components (Magento 2.3+) ### Basic UI Component ```javascript // web/js/view/checkout/summary/shipping-method.js define([ 'uiComponent', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/action/select-shipping-method' ], function (Component, quote, selectShippingMethodAction) { 'use strict'; return Component.extend({ defaults: { template: 'Magento_Checkout/shipping-method/shipping-method-list' }, isVisible: function () { return quote.shippingMethod() !== null; }, /** Get shipping method code */ getMethodCode: function () { var method = quote.shippingMethod(); return method ? method.carrier_code + '_' + method.method_code : ''; }, /** Select this shipping method */ selectMethod: function (method) { selectShippingMethodAction(method); } }); }); ``` ### XML UI Component Definition ```xml
sales_rule_form.sales_rule_form_data_source Cart Price Rules templates/form/collapsible