Magento Theme Development — Luma, Blank Theme and theme.xml
In this tutorial, you'll learn Magento theme development: the theme folder structure, theme.xml parent inheritance, creating a custom theme from the Blank theme, Less CSS compilation with Grunt, the file fallback chain, and assigning a theme in the admin panel.
What You'll Learn
- The required files and folder structure of a Magento theme
- Configuring theme.xml with parent theme and preview image
- Creating a custom theme inheriting from Magento Blank
- Understanding the theme inheritance chain and file fallback order
- Compiling Less CSS using Grunt, styles-l.less, and styles-m.less
- Running static content deployment for theme changes
- Assigning the theme in admin via Design Configuration
- How static file fallback works across modules, themes, and lib/web
Why It Matters
The default Luma theme is functional but generic. Every brand needs a distinct storefront. Theme development is how you customize the look and feel while preserving upgrade compatibility. By inheriting from Blank or Luma, you override only the files you need, and the rest comes from the parent. This keeps your theme small, maintainable, and upgrade-safe. Understanding the theme system is fundamental to any Magento frontend work.
Real-World Use
A boutique home decor brand wants a storefront that reflects its minimal aesthetic. The developer creates a custom child theme inheriting from Blank. They override the header template to add a logo, customize the Less variables to match the brand colors (beige backgrounds, olive green accents), add a custom homepage layout, and override the product page template to highlight the brand story. The entire custom theme consists of 12 files. When Magento releases a security update, the parent Blank theme is updated and the custom theme inherits all fixes automatically.
Learning Path
flowchart LR A["Marketing Tools"] --> B["Theme Development
You are here"]:::current B --> C["Layout XML"] C --> D["PHTML Templates"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Theme Folder Structure
A Magento theme lives in app/design/frontend/<Vendor>/<Theme>/. Let's create one called app/design/frontend/DodaTech/custom/.
The minimum required structure is:
app/design/frontend/DodaTech/custom/
├── etc/
│ └── view.xml
├── web/
│ ├── css/
│ │ ├── source/
│ │ │ └── _theme.less
│ │ ├── styles-l.less
│ │ └── styles-m.less
│ ├── images/
│ └── js/
├── media/
│ └── preview.jpg
├── registration.php
├── theme.xml
└── composer.json
Let's examine each file.
registration.php
Every theme must have a registration file that tells Magento the theme exists:
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::THEME,
'frontend/DodaTech/custom',
__DIR__
);
The first parameter specifies the component type (THEME). The second is the theme path (frontend/<Vendor>/<Theme>). The third is the directory path.
theme.xml
The theme.xml file defines the theme name, the parent theme, and an optional preview image:
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
<title>DodaTech Custom</title>
<parent>Magento/blank</parent>
<media>
<preview_image>media/preview.jpg</preview_image>
</media>
</theme>
- title — The human-readable name shown in admin.
- parent — The parent theme identifier (
Vendor/theme). Setting this toMagento/blankmeans your theme inherits all files from the Blank theme. You only need to create files you want to override. - preview_image — A screenshot that appears in the admin Design Configuration.
composer.json
The composer.json registers your theme as a Composer package:
{
"name": "dodatech/theme-custom",
"description": "DodaTech custom theme for Magento 2",
"type": "magento2-theme",
"version": "1.0.0",
"require": {
"magento/theme-frontend-blank": "*"
},
"autoload": {
"files": ["registration.php"]
}
}
view.xml
The etc/view.xml file defines image sizes for the theme:
<view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/view.xsd">
<media>
<images module="Magento_Catalog">
<image id="product_thumbnail_image" type="thumbnail">
<width>150</width>
<height>150</height>
</image>
<image id="product_base_image" type="image">
<width>600</width>
<height>600</height>
</image>
</images>
</media>
</view>
These dimensions control how product images are resized when rendered on the storefront.
Theme Inheritance Chain
When Magento looks for a file (template, layout, Less, static file), it walks the inheritance chain from the most specific to the most general.
Custom Theme (DodaTech/custom)
→ Parent Theme (Magento/blank)
→ Grandparent Theme (Magento/luma) [if luma is the parent instead]
→ Module view files (vendor/magento/module-catalog/view/frontend)
→ Lib/web (vendor/magento/theme-frontend-blank/web)
For a theme inheriting from Blank:
DodaTech/custom
→ Magento/blank
→ Module files (vendor/magento/module-*/view/frontend)
→ lib/web
This means your theme automatically gets all Blank theme templates, layouts, and styles. You only override what you want to change.
File Fallback Examples
Template fallback for Magento_Catalog::product/view.phtml:
1. DodaTech/custom/Magento_Catalog/templates/product/view.phtml
2. Magento/blank/Magento_Catalog/templates/product/view.phtml
3. vendor/magento/module-catalog/view/frontend/templates/product/view.phtml
Less file fallback for _typography.less:
1. DodaTech/custom/web/css/source/_typography.less
2. Magento/blank/web/css/source/_typography.less
3. vendor/magento/theme-frontend-blank/web/css/source/_typography.less
Magento uses the first matching file in the chain. This is how you selectively override specific parts without copying the entire parent theme.
Creating a Custom Theme
Let's walk through creating a complete custom theme step by step.
Step 1: Create the directory structure:
mkdir -p app/design/frontend/DodaTech/custom/etc
mkdir -p app/design/frontend/DodaTech/custom/web/css/source
mkdir -p app/design/frontend/DodaTech/custom/web/images
mkdir -p app/design/frontend/DodaTech/custom/web/js
mkdir -p app/design/frontend/DodaTech/custom/media
Step 2: Create registration.php:
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::THEME,
'frontend/DodaTech/custom',
__DIR__
);
Step 3: Create theme.xml:
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
<title>DodaTech Custom</title>
<parent>Magento/blank</parent>
<media>
<preview_image>media/preview.jpg</preview_image>
</media>
</theme>
Step 4: Create composer.json:
{
"name": "dodatech/theme-custom",
"description": "DodaTech custom theme",
"type": "magento2-theme",
"version": "1.0.0",
"require": {
"magento/theme-frontend-blank": "*"
},
"autoload": {
"files": ["registration.php"]
}
}
Step 5: Create etc/view.xml:
<view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/view.xsd">
<media>
<images module="Magento_Catalog">
<image id="product_thumbnail_image" type="thumbnail">
<width>150</width>
<height>150</height>
</image>
<image id="product_base_image" type="image">
<width>600</width>
<height>600</height>
</image>
<image id="product_small_image" type="small_image">
<width>60</width>
<height>60</height>
</image>
</images>
</media>
</view>
Step 6: Register and deploy the theme:
# Register the theme in the database
bin/magento setup:upgrade
# Clear cache
bin/magento cache:clean
# Deploy static files (development mode)
bin/magento setup:static-content:deploy -f
Step 7: Assign the theme in admin:
Stores > Configuration > Design > Design Theme
Scope: Default Config
Design Theme: DodaTech Custom
Less CSS Compilation
Magento uses Less for CSS preprocessing. The theme defines two main Less files that are compiled into CSS.
styles-l.less and styles-m.less
- styles-l.less — Compiled to
styles-l.css(large screens / desktop) - styles-m.less — Compiled to
styles-m.css(mobile-first base styles)
Both files import the same core library files and add theme-specific styles.
_theme.less
The _theme.less file is where you override design variables. Create it at web/css/source/_theme.less:
// Brand colors
@color-primary: #2d5a27; // Olive green
@color-secondary: #f5f0eb; // Beige
@color-accent: #c17817; // Warm gold
// Typography
@font-family-name__base: 'Open Sans';
@font-size__base: 16px;
@color-text: #333333;
// Layout
@layout__max-width: 1280px;
@layout-indent__width: 20px;
// Buttons
@button-primary__background: @color-primary;
@button-primary__hover__background: #1e3f1a;
@button-primary__color: #ffffff;
// Links
@link__color: @color-accent;
@link__hover__color: darken(@color-accent, 10%);
These variables affect every component that uses them. By changing @color-primary, you change primary buttons, header backgrounds, and active states throughout the storefront.
_extend.less
Use _extend.less to add or override styles without editing core Less files:
// web/css/source/_extend.less
// Custom homepage hero styles
.home-hero {
background-color: @color-secondary;
padding: 40px 0;
text-align: center;
h1 {
font-size: 36px;
color: @color-primary;
}
}
// Custom footer styles
.footer {
background-color: @color-primary;
color: #ffffff;
padding: 30px 0;
}
Compiling with Grunt
Magento provides Grunt configuration for automatic Less compilation during development.
Install Grunt:
npm install
Configure Grunt:
Edit dev/tools/grunt/configs/themes.js and add your theme:
module.exports = {
dodatech_custom: {
area: 'frontend',
name: 'DodaTech/custom',
locale: 'en_US',
files: [
'css/styles-m',
'css/styles-l'
],
dsl: 'less'
}
};
Run Grunt:
# Watch mode (recompiles on every change)
grunt watch:dodatech_custom
# One-time compilation
grunt exec:dodatech_custom
grunt less:dodatech_custom
Manual Compilation
If you do not use Grunt, compile Less with {{< ilink "PHP" }} commands:
# For production/deployment
bin/magento setup:static-content:deploy -f
In developer mode, Magento compiles Less on the fly. In production mode, you must run setup:static-content:deploy to generate the CSS files.
Static File Fallback
When a static file (CSS, JS, image) is requested, Magento searches in this order:
1. Theme web/ directory (e.g., DodaTech/custom/web/)
2. Parent theme web/ directory (Magento/blank/web/)
3. Module view files (vendor/magento/module-catalog/view/frontend/web/)
4. lib/web/ (vendor/magento/framework/view/lib/web/)
For example, a request for js/custom.js looks in:
1. app/design/frontend/DodaTech/custom/web/js/custom.js
2. app/design/frontend/Magento/blank/web/js/custom.js
3. vendor/magento/module-*/view/frontend/web/js/custom.js
4. lib/web/js/custom.js
This fallback lets themes override any static file without modifying core.
Theme Configuration in Admin
After creating the theme, assign it in the admin:
- Go to Stores > Configuration > Design.
- Under Design Theme, select your theme.
- Click Save Config.
You can set different themes for different store views. For example:
Scope: Default Config
Design Theme: DodaTech Custom
Scope: Spanish Store View
Design Theme: DodaTech Custom Spanish
Theme Preview Image
Create a media/preview.jpg screenshot of your theme. This image appears in the admin when selecting a theme. Use a 1200x800 pixel screenshot that shows the homepage.
Common Mistakes
Forgetting
registration.php. Without this file, Magento does not recognize the theme. The theme does not appear in the admin Design Configuration. Always createregistration.phpfirst.Setting the wrong parent theme in
theme.xml. If you set parent toMagento/lumawhen you intendedMagento/blank, your theme inherits all Luma styles and templates. Use Blank as the parent for cleaner customization, or Luma if you want the Luma look as your starting point.Editing core Less files instead of
_extend.less. Modifyingvendor/magento/theme-frontend-blank/web/css/source/_theme.lessdirectly will be overwritten on upgrade. Always use your theme's_extend.lessor_theme.lessfiles. This keeps your customizations separate and upgrade-safe.Not running static content deployment in production mode. In production mode, Magento serves pre-compiled files. Changes to Less files in your theme do not appear until you run
bin/magento setup:static-content:deploy -f. Always deploy after style changes.Copying the entire parent theme instead of inheriting. Some developers copy the entire Blank theme into their custom theme. This defeats the purpose of inheritance. Your theme should contain only the files you are overriding. Inherit everything else from the parent.
Practice Questions
What files are required for a Magento theme to be recognized? Answer: The minimum required files are
registration.phpandtheme.xml. Additionally,composer.jsonis strongly recommended for the theme to be managed as a Composer package. Withoutregistration.php, the theme does not appear in the admin.How does the file fallback chain work for templates? Answer: Magento checks the active theme directory first, then the parent theme directory, then the module's view/frontend directory, then lib/web. The first matching file is used. This allows a custom theme to override only specific templates while inheriting all others from the parent theme.
What is the purpose of
_theme.lessvs_extend.less? Answer:_theme.lessis for overriding Less variables (colors, fonts, spacing) that affect the entire storefront._extend.lessis for adding custom CSS rules, overriding component styles, or adding new page-specific styles without modifying core Less files.Challenge: Build a complete custom theme from scratch. Create the full directory structure and all required files for a theme named "DodaTech/outdoor" that inherits from Magento/blank. Customize the brand colors (forest green, brown, cream), set a custom layout width of 1280px, change the base font to "Roboto" at 16px, and customize the button colors to match the brand. Create a preview image. Register the theme with
bin/magento setup:upgradeand assign it in the admin. Override the header template (Magento_Theme::html/header.phtml) to add a custom logo. Override_extend.lessto style the homepage hero section. Run Grunt watch for Live less compilation and verify changes appear in the browser. Write a {{< ilink "PHP" }} script using\Magento\Theme\Model\ResourceModel\Theme\CollectionFactoryto read all registered themes from thetheme{{< ilink "MySQL" }} table and display them with their parent themes.
FAQ
Mini Project
Your task: Build a complete custom theme for "DodaTech/outdoor" — an outdoor gear store with a nature-inspired design.
- Create the full directory structure for
app/design/frontend/DodaTech/outdoor/. - Create all required files:
registration.php,theme.xml(parent: Magento/blank),composer.json,etc/view.xml(image sizes: 300x300 thumbnails, 800x800 base, 100x100 small). - Create
web/css/source/_theme.lesswith these variables:- Primary color: #2e5c2e (forest green)
- Secondary: #f4f1ea (cream)
- Accent: #8b5e3c (brown)
- Font: 'Lato', sans-serif, 15px
- Layout max width: 1200px
- Create
web/css/source/_extend.lesswith custom styles for:- A branded homepage hero section
- Custom product page layout
- Footer with dark background
- Create a custom header template override at
Magento_Theme/templates/html/header.phtmlthat replaces the default logo area with a custom brand header. - Add a preview image in
media/preview.jpg. - Register the theme with
bin/magento setup:upgradeand assign it to the default config. - Run
bin/magento setup:static-content:deploy -fand verify the theme loads in the frontend. - Write a Magento PHP script using
\Magento\Theme\Model\Theme\Collectionto list all registered frontend themes and their parent relationships.
What's Next
Now that you understand theme development, learn how to customize page layouts with XML:
Continue to Lesson 22: Layout XML — Layout handles, containers, and blocks.
Related lessons:
- Layout XML — Customizing page structure in your theme
- PHTML Templates — Creating custom template files
- CSS and JavaScript — Advanced frontend customization
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro