Skip to content

Magento Theme Development — Luma, Blank Theme and theme.xml

DodaTech Updated 2026-06-27 11 min read

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 to Magento/blank means 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:

  1. Go to Stores > Configuration > Design.
  2. Under Design Theme, select your theme.
  3. 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

  1. Forgetting registration.php. Without this file, Magento does not recognize the theme. The theme does not appear in the admin Design Configuration. Always create registration.php first.

  2. Setting the wrong parent theme in theme.xml. If you set parent to Magento/luma when you intended Magento/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.

  3. Editing core Less files instead of _extend.less. Modifying vendor/magento/theme-frontend-blank/web/css/source/_theme.less directly will be overwritten on upgrade. Always use your theme's _extend.less or _theme.less files. This keeps your customizations separate and upgrade-safe.

  4. 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.

  5. 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

  1. What files are required for a Magento theme to be recognized? Answer: The minimum required files are registration.php and theme.xml. Additionally, composer.json is strongly recommended for the theme to be managed as a Composer package. Without registration.php, the theme does not appear in the admin.

  2. 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.

  3. What is the purpose of _theme.less vs _extend.less? Answer: _theme.less is for overriding Less variables (colors, fonts, spacing) that affect the entire storefront. _extend.less is for adding custom CSS rules, overriding component styles, or adding new page-specific styles without modifying core Less files.

  4. 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:upgrade and assign it in the admin. Override the header template (Magento_Theme::html/header.phtml) to add a custom logo. Override _extend.less to 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\CollectionFactory to read all registered themes from the theme {{< ilink "MySQL" }} table and display them with their parent themes.

FAQ

Can I use CSS instead of Less in my theme?

You can add plain CSS files in your theme's web/css/ directory, but the core Magento styling system uses Less. Mixing both works, but you will miss out on Less features like variables and mixins. Add custom CSS by creating a file like web/css/custom.css and including it via layout XML.

What is the difference between developer mode and production mode for themes?

In developer mode, Magento compiles Less on each page load, which is slow but allows instant style feedback. Static files are not cached aggressively. In production mode, Magento serves pre-compiled static files from pub/static. You must run bin/magento setup:static-content:deploy after any Less change.

How do I include a custom JavaScript file in my theme?

Create the JS file in your theme's web/js/ directory (e.g., web/js/custom.js). Then create a default_head_blocks.xml layout file in your theme's Magento_Theme/layout/ directory with: . This adds the script to every page.

Can I have multiple themes active at the same time?

Yes. Each store view can have its own theme assignment. Go to Stores > Configuration > Design, select a store view scope, and choose a different theme. This is useful for A/B testing or offering different designs per language.

What happens when a parent theme is updated?

Your child theme inherits the updates automatically because it loads files from the parent theme through the fallback chain. However, if you overrode a file that the parent update modified, you need to manually review and update your override to incorporate security patches or new features.

Mini Project

Your task: Build a complete custom theme for "DodaTech/outdoor" — an outdoor gear store with a nature-inspired design.

  1. Create the full directory structure for app/design/frontend/DodaTech/outdoor/.
  2. Create all required files: registration.php, theme.xml (parent: Magento/blank), composer.json, etc/view.xml (image sizes: 300x300 thumbnails, 800x800 base, 100x100 small).
  3. Create web/css/source/_theme.less with these variables:
    • Primary color: #2e5c2e (forest green)
    • Secondary: #f4f1ea (cream)
    • Accent: #8b5e3c (brown)
    • Font: 'Lato', sans-serif, 15px
    • Layout max width: 1200px
  4. Create web/css/source/_extend.less with custom styles for:
    • A branded homepage hero section
    • Custom product page layout
    • Footer with dark background
  5. Create a custom header template override at Magento_Theme/templates/html/header.phtml that replaces the default logo area with a custom brand header.
  6. Add a preview image in media/preview.jpg.
  7. Register the theme with bin/magento setup:upgrade and assign it to the default config.
  8. Run bin/magento setup:static-content:deploy -f and verify the theme loads in the frontend.
  9. Write a Magento PHP script using \Magento\Theme\Model\Theme\Collection to 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:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro