Skip to content

Admin Styles

Available since Hyvä CMS 1.3.0

Package-owned styles and the combined admin build are available from Hyvä CMS 1.3.0.

Hyvä CMS integrations ship their own compiled admin CSS. Installing a package gives the editor the styles it needs without requiring an npm build on the site. An optional deployment command combines the styles for the packages you actually use.

This page covers the Liveview Editor interface. For CSS generated from storefront content, see Tailwind CSS Compilation.

Why Packages Ship Their Own Styles

Building one stylesheet against every possible integration would require those packages to be installed together at build time. It would also tie a new package's appearance to a successful rebuild of the editor's stylesheet.

Instead, each package ships a compiled fragment containing its utilities and feature-specific rules. The host, such as the Liveview Editor, supplies the shared foundations: colours, tokens, resets, buttons and form controls. A fragment uses that shared design language without shipping another copy of the host's foundations.

Some generated utilities repeat across fragments. That is a deliberate trade-off: a forgotten optimization step or an npm failure should not leave an otherwise correctly deployed editor without styles until the next deployment. The package CSS remains available as the fallback.

The optional combined build, called an aggregate, removes that repetition. If a site enables two participating extensions, the build uses those extensions' sources and the host, rather than requiring every CMS integration to be installed. Tailwind generates their required utilities together. This reduces repeated utility CSS; it does not remove arbitrary unused hand-written component rules.

Add the Combined Build to Deployment

Use a CMS version that includes Hyva_CmsAdminStyles. From the Magento root, after installing the final packages, enabling the required modules and deploying static content, run:

bin/magento cache:clean config
bin/magento hyva:cms:admin-styles:build --install

The configuration-cache clean lets the registry discover added, removed or changed module manifests. Only enabled Magento modules participate. The command builds each available surface, such as liveview-editor, and skips a surface whose host is not installed.

The build needs Node.js 20 or later for the Liveview host, npm, and write access to var and pub/static. --install runs npm ci --ignore-scripts in the host's Tailwind directory using its lockfile, so it needs access to the configured npm registry. Serving the resulting CSS does not require Node.

If the compiler dependencies are already installed, omit --install. To select the compiler explicitly:

bin/magento hyva:cms:admin-styles:build --compiler=/absolute/path/to/node_modules/.bin/tailwindcss

The current style contract requires Tailwind CSS 4.1.18. An explicit compiler takes precedence over --install; the host's imported CSS dependencies must still be available.

The command compiles the host source and registered module.css files together. It does not concatenate prebuilt CSS. It writes one stylesheet and a matching manifest per surface:

pub/static/_cache/hyva-cms-admin-styles/<content-hash>/<surface>/
├── styles.css
└── manifest.json

Keep both files in the deployed artifact and distribute them to every application node. Run the command after any step that clears pub/static, otherwise that step will remove the optimization output.

How the Fallback Works

State Styles used
A matching aggregate and manifest exist One combined stylesheet for that surface replaces the registered host and fragment assets.
The build has never run, or its output is missing The shipped host stylesheet followed by the enabled packages' fragments.
Installed inputs changed, or the manifest is invalid The shipped host and fragments until a matching aggregate is built.
Compilation fails The command reports failure. It does not remove the shipped CSS; the loader can still use matching existing output or the fallback.

The loader checks the aggregate against the installed inputs, including shared tokens, host and package source files, scanned content and shipped fragments. Rebuild after changing those inputs.

The fallback depends on packages shipping current compiled CSS and on normal Magento static-asset deployment working. The optimization command does not regenerate a package's fallback file. If you edit package templates or CSS, rebuild and ship that package's fragment as well.

Shared Styles and Tokens

These paths are relative to hyva-themes/commerce-module-cms:

Path Purpose
src/admin-styles/view/adminhtml/tailwind/hyva.config.json Shared palette source.
src/admin-styles/view/adminhtml/tailwind/generated/hyva-tokens.css Generated palette CSS shipped with CMS.
src/admin-styles/view/adminhtml/tailwind/tokens.css Palette imports, semantic tokens, typography and breakpoints.
src/admin-styles/view/adminhtml/tailwind/components/ Shared buttons, forms, cards, tooltips and other controls.
src/admin-styles/view/adminhtml/tailwind/utilities/ Shared custom utilities.
src/admin-styles/view/adminhtml/tailwind/host.css Foundations imported by a host.
src/admin-styles/view/adminhtml/tailwind/theme.css Reference-only tokens and utilities for package fragment builds.

Use shared classes such as btn btn-primary, form-input and card in your templates. Keep feature-specific selectors in your own module. Avoid copying shared controls or redefining global tokens in each package; both build modes should use the same design definitions.

Add a Custom Module

The example uses the Magento module Acme_Feature. Place these files below its registered module root, whether that is app/code/Acme/Feature or a Composer package's src directory:

view/adminhtml/
├── templates/panel.phtml
├── tailwind/
│   ├── hyva-cms-admin.json
│   ├── module.css
│   ├── fragment.css
│   ├── build.mjs
│   ├── package.json
│   └── package-lock.json
└── web/css/styles.css

Declare a dependency on a CMS release providing admin style contract version 1.

Register the Styles

In view/adminhtml/tailwind/hyva-cms-admin.json:

{
  "schema": 1,
  "contract": 1,
  "entries": [
    {
      "surfaces": ["liveview-editor"],
      "source": "module.css",
      "asset": "Acme_Feature::css/styles.css"
    }
  ]
}

source is relative to the manifest and must not contain ... The asset must belong to the declaring Magento module and exist under its view/adminhtml/web/css directory. The schema is src/admin-styles/etc/hyva-cms-admin.schema.json in the CMS package.

Hosts discover this manifest automatically. You do not need to add your templates to the editor's source scan or hard-code your stylesheet into its asset allowlist.

Declare Your Sources and Rules

In view/adminhtml/tailwind/module.css:

@source "../templates";
@source "../web/js";

@layer components {
    .acme-feature-panel {
        @apply rounded-md border border-nimbus-200 bg-white p-4;
    }
}

Scan only your module's content. Source paths are relative to the CSS file that declares them. Include JavaScript when it contains class names, and use complete strings such as bg-meadow-100 instead of dynamically assembling bg-${color}-100. For specific classes absent from scanned files, declare candidates with @source inline("bg-meadow-100 bg-volcano-100");.

Namespace custom selectors and place them in @layer components. Do not redefine body, .btn, .grid or other global selectors. The aggregate imports module.css directly, so keep it free of standalone compiler setup and machine-specific paths.

Mark the Rendered Roots

In view/adminhtml/templates/panel.phtml:

<section data-hyva-cms-styles="acme-feature" class="acme-feature-panel grid gap-4">
    <label class="grid gap-2">
        <span>Title</span>
        <input class="form-input" type="text" name="title">
    </label>
    <button class="btn btn-primary" type="button">Apply</button>
</section>

Use the same scope value in the build script below. It must start with a lowercase letter and contain only lowercase letters, digits and hyphens. Add it to every rendered package root, including teleported dialogs. A marker on an inert Alpine <template> does not cover its rendered children elsewhere.

The fragment build scopes generated utilities to these roots and their descendants, excluding nested roots belonging to another package. It does not rewrite your custom component selectors, which still need their namespace. Aggregates generate ordinary utility selectors, so shared utility names must have consistent meanings across packages.

Build the Shipped Fragment

In view/adminhtml/tailwind/fragment.css:

@layer base, theme, components, utilities;
@import "tailwindcss/theme.css" theme(reference);
@import "./theme-reference.css";
@import "tailwindcss/utilities.css" layer(utilities) source(none);
@import "./module.css";

The reference file generated below makes CMS tokens and utilities available without emitting another copy of the shared foundations. Do not import host.css or the full tailwindcss entry point into a fragment.

In view/adminhtml/tailwind/package.json:

{
  "name": "@acme/feature-admin-styles",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "node build.mjs",
    "watch": "node build.mjs --watch"
  },
  "devDependencies": {
    "@tailwindcss/cli": "4.1.18",
    "tailwindcss": "4.1.18",
    "postcss": "8.5.6",
    "postcss-selector-parser": "7.1.0"
  },
  "engines": { "node": ">=20.0.0" }
}

In view/adminhtml/tailwind/build.mjs:

import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

const directory = dirname(fileURLToPath(import.meta.url));
if (!process.env.HYVA_CMS_MODULE_PATH) {
    throw new Error('Set HYVA_CMS_MODULE_PATH to the CMS Composer package root.');
}
const cms = resolve(process.env.HYVA_CMS_MODULE_PATH);
const theme = resolve(cms, 'src/admin-styles/view/adminhtml/tailwind/theme.css');
if (!existsSync(theme)) throw new Error('CMS admin style contract not found.');
writeFileSync(
    resolve(directory, 'theme-reference.css'),
    `@import ${JSON.stringify(theme.replaceAll('\\', '/'))};\n`
);
mkdirSync(resolve(directory, '../web/css'), { recursive: true });
const runner = resolve(cms, 'src/admin-styles/build/build-styles.mjs');
const { buildStyles } = await import(pathToFileURL(runner).href);
await buildStyles({
    directory,
    input: 'fragment.css',
    output: '../web/css/styles.css',
    scope: 'acme-feature',
    watch: process.argv.includes('--watch'),
});

From your module's Tailwind directory:

export HYVA_CMS_MODULE_PATH=/absolute/path/to/vendor/hyva-themes/commerce-module-cms
npm install
npm run build

Commit the resulting lockfile and use npm ci for subsequent builds and CI. Use npm run watch during development; both modes apply the same utility scoping. Ignore node_modules/ and theme-reference.css in Git, since the latter contains a local absolute path.

Ship the manifest, original CSS, scanned content, build files, lockfile and generated web/css/styles.css. Clear Magento's configuration cache after registration. Check your UI with host plus fragments, then with a combined build, including responsive states and teleported dialogs. Keep the generated fragment current in package releases so either mode renders correctly.

Troubleshooting

Problem Check
A package has no styles Enabled module, manifest location and validity, shipped CSS, static deployment and configuration cache.
A utility works only in the aggregate Package @source paths, full class names, compiled fragment and scope markers on rendered roots.
The aggregate is not selected Both output files exist and the installed inputs still match. Rebuild after package or source changes.
Compilation fails Required compiler version, locked host dependencies, npm registry access and filesystem permissions. The shipped fragments remain the fallback.
The custom build cannot find CMS HYVA_CMS_MODULE_PATH points to the Composer package root containing src/admin-styles.