Skip to content

MageObsidian Theme

MageObsidian Theme is the reference storefront theme built entirely with MageObsidian Components. It replaces Magento's Luma frontend with a modern stack β€” Twig templates, Vite, TailwindCSS 4, and lazy-hydrated Vue islands β€” while keeping Magento's native layouts, blocks, and view models. It is already functional and covers the same ground as Luma (catalog, checkout, customer, search, cart, wishlist, compare, reviews, and sales).

See OBSIDIAN live

This page is a practical guide to using and extending the theme. For the underlying mechanism shared by every compatible theme, see Compatibility Themes.

Compatibility

  • βœ… Magento Open Source 2.4.7+ β€” supported today.
  • πŸ§ͺ Adobe Commerce and Mage-OS β€” technically compatible since core 2.6.0, but not fully tested yet.

Architecture: two layers

The theme ships as two stacked themes so design is cleanly separated from plumbing:

  • MageObsidian/theme-base


    The neutral, technical foundation: build wiring and structural templates, without any design tokens. This is the parent you usually inherit from when starting a brand-new look.

    Package: mage-obsidian/theme-base

    View source

  • MageObsidian/default


    The OBSIDIAN skin: design tokens, styled Twig templates, and a replaceable apparel demo layer. Inherit from it when you want to keep OBSIDIAN and tweak; replace its tokens to rebrand.

    Package: mage-obsidian/theme-default

    View source

The storefront logic β€” view models, legacy-layout neutralization, and shared Vue islands β€” lives in the mage-obsidian/module-storefront repository, pulled in automatically by theme-base.

Inheritance is declared with Magento's native theme.xml:

1
2
3
4
5
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
    <title>MageObsidian β€” Default (Obsidian)</title>
    <parent>MageObsidian/theme-base</parent>
</theme>

A theme directory looks like this:

app/design/frontend/MageObsidian/default/
β”œβ”€β”€ theme.xml                         # title + parent
β”œβ”€β”€ registration.php
β”œβ”€β”€ etc/
β”‚   └── mage_obsidian_compatibility.xml   # opt-in to the MageObsidian build
β”œβ”€β”€ web/
β”‚   β”œβ”€β”€ theme.config.js               # build options (ESM)
β”‚   β”œβ”€β”€ css/
β”‚   β”‚   └── theme.source.css          # @theme design tokens + global styles
β”‚   └── generated/                    # Vite build output (do not edit)
└── Magento_Theme/                    # per-module Twig overrides
    └── templates/
        └── html/
            β”œβ”€β”€ header.twig
            └── footer.twig

Install & activate

  1. Require the theme with Composer. mage-obsidian/theme-default pulls in theme-base, the modern-frontend engine, and the full storefront module stack (Luma-level parity) automatically:

    composer require mage-obsidian/theme-default
    bin/magento setup:upgrade
    
  2. Select it in the Admin under Stores β†’ Configuration β†’ General β†’ Design β†’ Design Theme (or via bin/magento config:set), then apply it to your store view.

  3. Regenerate the PHP ↔ JS contract so the build engine sees the theme:

    bin/magento mage-obsidian:frontend:config --generate
    
  4. Build the frontend assets to disk:

    mage-obsidian:build-themes --theme <theme>   # omit --theme to build all
    

See Installation for first-time setup.


Develop with live reload (HMR)

For day-to-day work, run the Vite dev server so edits to .vue, CSS, and Twig hot-reload in the browser. Enable HMR and wire nginx once, then start the server per session:

1
2
3
4
bin/magento deploy:mode:set developer
bin/magento mage-obsidian:frontend:hmr --enable                # once: HMR is a developer-mode flag
bin/magento mage-obsidian:frontend:dev --print-nginx           # once: paste the snippet into your nginx server block
bin/magento mage-obsidian:frontend:dev --start --theme=Vendor/theme   # per session

The nginx snippet proxies /static requests to the Vite dev server β€” host and port come from your config, you don't hand-pick them. HMR is ignored in production mode. Full walkthrough and troubleshooting in HMR Configuration and Development Workflow.


Deploy to production

There is no separate build step. MageObsidian hooks into Magento's standard static-content deploy: its plugins exclude modern themes from the legacy Less/RequireJS pipeline and inject the Vite output (minified, tree-shaken, hashed) as part of the normal command:

bin/magento deploy:mode:set production
bin/magento setup:static-content:deploy

Only themes shipping etc/mage_obsidian_compatibility.xml go through the Vite pipeline; everything else follows Magento's native deploy untouched. See Building Static Assets.


Re-theme it: design tokens

Re-skinning OBSIDIAN is CSS-first: every color, font, radius, and easing lives in the @theme block of web/css/theme.source.css. Change these tokens and the whole theme follows β€” no JavaScript config involved.

@import "tailwindcss";

@theme {
  /* β€” Obsidian: vitreous near-black, cool violet undertone β€” */
  --color-obsidian-950: #08070b;
  --color-obsidian-900: #0d0c12;
  --color-obsidian-800: #16151d;

  /* β€” Alabaster: cool mineral light base (not warm cream) β€” */
  --color-alabaster: #eceaf0;
  --color-alabaster-raised: #f6f5f8;
  --color-paper: #ffffff;

  /* β€” Ink (text) + restrained functional accents β€” */
  --color-ink: #141319;
  --color-accent: #2f6e66;
  --color-sale: #9a5b3f;

  /* β€” Typography β€” */
  --font-display: "Bodoni Moda", Georgia, serif;
  --font-body: "Hanken Grotesk", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;

  /* β€” Sharpened conchoidal edge + signature easing β€” */
  --radius-edge: 2px;
  --ease-obsidian: cubic-bezier(0.22, 0.61, 0.36, 1);
}

Templates consume these as Tailwind utilities (font-display, text-ink, bg-alabaster, rounded-edge, …), so editing a token re-flows every screen at once.

Don't edit default in place

To rebrand, create your own child theme (below) and override only the tokens you need. That keeps your changes upgrade-safe. For the full set of CSS options and module-CSS exclusion, see CSS Configuration.


Light and dark appearance

OBSIDIAN can offer the shopper a light, automatic or dark storefront. It ships turned off: a dark storefront is a brand decision, and catalogue photography on a white background looks very different against a dark page. Check yours before you enable it.

bin/magento config:set mage_obsidian/appearance/enabled 1
bin/magento cache:flush

Stores β†’ Configuration β†’ MageObsidian β†’ Frontend β†’ Appearance β†’ Enable the Appearance Selector

Enabled, a three-state control appears in the header β€” sun, monitor, moon β€” with the automatic state in the middle. Automatic follows the operating system and keeps following it if the shopper changes it mid-visit. The choice lives in localStorage, never in a cookie.

It never varies the served document

The appearance is decided in the browser, after the HTML has been delivered. Two shoppers with opposite appearances receive the same bytes, so a page stays as cacheable as it was: no X-Magento-Vary, no duplicated Varnish entries, no Vary header of its own.

A small inline script in the <head> β€” emitted through Magento's SecureHtmlRenderer, so it carries the CSP nonce β€” reads the stored choice and stamps data-theme on <html> before the first paint. The appearance tokens are inlined next to it, so the first frame is already correct even while the deferred stylesheet is still in flight.

The automatic state needs JavaScript

The script is what translates the system preference into an appearance. With scripts disabled the storefront stays light. That is also what makes the off switch absolute: no script, no data-theme, no dark appearance.

The dark palette

The dark appearance redefines the same tokens rather than introducing new ones, so every template that already reads text-ink or bg-alabaster follows along untouched. The palette lives in its own file, web/css/appearance.css:

:root {
  --dark-page: #08070b;
  --dark-surface: #1a1922;
  --dark-surface-raised: #211f2a;
  --dark-text: #ecebf0;
  --dark-accent: #45a195;
}

:root[data-theme="dark"] {
  --color-page: var(--dark-page);
  --color-alabaster: var(--dark-surface);
  --color-alabaster-raised: var(--dark-surface-raised);
  --color-ink: var(--dark-text);
  --color-accent: var(--dark-accent);

  color-scheme: dark;
}

The indirection is deliberate. Token names describe the light appearance β€” --color-ink means "dark ink", --color-alabaster means "pale stone" β€” so in the dark appearance their values say the opposite of their names. Assigning them from role variables (--dark-text, --dark-surface) is what keeps that readable instead of looking like a mistake.

To rebrand the dark appearance, override the role variables in your child theme; you never need to touch the mapping.

Two things to check on a dark storefront

Product photography. Catalogue images with white backgrounds become bright rectangles on a dark page. No token fixes this β€” decide whether to mitigate it or accept it, with the grid in front of you.

Surface separation. Contrast ratio is a text-legibility metric and compresses between dark surfaces: two surfaces a shopper distinguishes easily can measure 1.1:1. Compare perceptual lightness (CIELAB L*) instead β€” OBSIDIAN keeps adjacent surfaces at least 7 L* apart, the same separation its light palette already uses.


Build your own theme on top

Create a theme under app/design/frontend/Vendor/Custom/ and inherit from theme-base (clean slate) or default (keep OBSIDIAN and tweak):

theme-base or default?

Inherit from default when OBSIDIAN is close to what you want β€” retheme its tokens and override a few templates. It's the fastest path and you keep the styled, parity-complete storefront. Start from theme-base only when you want a clean slate and will design every surface from zero.

  1. registration.php and theme.xml with the parent of your choice:

    1
    2
    3
    4
    5
    <theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
        <title>Acme β€” Storefront</title>
        <parent>MageObsidian/theme-base</parent>
    </theme>
    
  2. Opt in to the MageObsidian build with etc/mage_obsidian_compatibility.xml:

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="urn:magento:module:MageObsidian_ModernFrontend:etc/xsd/mage_obsidian_theme_compatibility.xsd">
        <features>
            <compatibility>true</compatibility>
        </features>
    </config>
    
  3. web/theme.config.js β€” build options (ESM). Tokens go in CSS, not here:

    1
    2
    3
    4
    5
    6
    7
    export default {
        includeParentThemes: false,
        ignoredCssFromModules: [],
        exposeNpmPackages: [
            { package: 'pinia', exposePath: 'pinia' },
        ],
    }
    
  4. web/css/theme.source.css β€” your @theme tokens (and any overrides).

Then run --generate and mage-obsidian:build-themes again. Full reference: Compatibility Configuration and Theme Configuration.


Override templates (Twig)

Templates live at <Vendor>_<Module>/templates/**/*.twig. To override one, recreate the same path in your theme β€” Magento's fallback resolves the most specific version. For example, the page title:

{# Page heading (<h1>). `block` is Magento\Theme\Block\Html\Title. #}
{% set heading = block.getPageHeading() %}
{% if heading|trim %}
<div class="page-title-wrapper mb-6{{ block.getCssClass() ? ' ' ~ block.getCssClass() }}">
    <h1 class="page-title font-display text-3xl leading-tight tracking-[0.01em] text-ink md:text-4xl">
        <span class="base" data-ui-id="page-title-wrapper" {{ block.getAddBaseAttribute()|raw }}>{{ heading }}</span>
    </h1>
    {{ child_html() }}
</div>
{% endif %}

Notice the design tokens surfacing as utility classes (font-display, text-ink). For the Twig runtime, helpers, and filters, see Twig Engine and Helpers & Filters.


Vue islands & JavaScript

The default theme mounts interactive Vue islands for the dynamic bits β€” header search, cart / wishlist / compare counts, the mobile menu, the store switcher, drawers, and toasts β€” each hydrated lazily so the page stays fast. You extend the theme by adding or overriding components and exposing the NPM packages you need (Pinia is exposed out of the box).


What's included (Luma parity)

The theme covers the same storefront surface as Luma, rebuilt in Twig + Vue:

  • Chrome: header, footer, navigation, mobile menu, store/currency switcher.
  • Catalog: category listing, layered navigation, product detail page, swatches.
  • Purchase: cart, checkout, instant purchase, gift messages, multishipping.
  • Account: customer login & account, wishlist, compare, reviews, sales/orders.
  • Search: quick search with autocomplete.

Next steps

  • Compatibility Themes


    The full mechanism behind compatible themes: compatibility, CSS, config, and components.

    Overview

  • Twig Engine


    Write and override templates in Twig, with helpers and filters.

    Explore Twig

  • Getting started


    Install the components and build a theme from scratch.

    Requirements