Skip to content

Instantly share code, notes, and snippets.

@hughc
Last active June 12, 2026 00:37
Show Gist options
  • Select an option

  • Save hughc/c2b05067b4882407e97ce81e29d9257d to your computer and use it in GitHub Desktop.

Select an option

Save hughc/c2b05067b4882407e97ce81e29d9257d to your computer and use it in GitHub Desktop.
Extract Elementor v4 Atomic global classes to CSS
<?php
/**
* Generate a CSS file from Elementor's global classes & variables stored in the default kit.
*
* Looks up the post with name='default-kit' / post_type='elementor_library',
* reads `_elementor_global_classes` and `_elementor_global_variables` meta,
* generates CSS, and writes it to a file relative to the active theme directory.
*
* @param string $filename Filename (or path) relative to the active theme directory, e.g. 'assets/css/elementor-global.css'
* @return string|false The absolute path the file was written to, or false on failure.
*/
function generate_elementor_global_css_file(string $filename): string|false
{
// 1. Find the default kit post
$kit_post = get_posts([
'post_type' => 'elementor_library',
'name' => 'default-kit',
'posts_per_page' => 1,
'post_status' => 'any',
]);
if (empty($kit_post)) {
return false;
}
$post_id = $kit_post[0]->ID;
// 2. Source data from meta keys
$classesJson = get_post_meta($post_id, '_elementor_global_classes', true);
$variablesJson = get_post_meta($post_id, '_elementor_global_variables', true);
if (!$classesJson || !$variablesJson) {
return false;
}
$classesData = json_decode($classesJson, true);
$variablesData = json_decode($variablesJson, true);
if (!$classesData || !$variablesData) {
return false;
}
// 3. Generate CSS
$css = build_css($classesData, $variablesData);
if ($css === '') {
return false;
}
// 4. Write to file relative to active theme directory
$theme_dir = get_stylesheet_directory();
$target_path = trailingslashit($theme_dir) . ltrim($filename, '/');
// Ensure parent directory exists
$parent_dir = dirname($target_path);
if (!is_dir($parent_dir)) {
wp_mkdir_p($parent_dir);
}
$bytes = file_put_contents($target_path, $css);
if ($bytes === false) {
return false;
}
return $target_path;
}
/**
* Generate the CSS string from the decoded class/variable data structures.
*/
function build_css(array $classesData, array $variablesData): string
{
$variables = $variablesData['data'] ?? [];
$classes = $classesData['items'] ?? [];
$varMap = build_variable_map($variables);
$selector_prefix = 'div.elementor';
$css = [];
// 1. Export CSS custom properties from global variables (named by label)
$css[] = ':root {';
foreach ($variables as $id => $var) {
$colorValue = resolve_color_value($var['value'] ?? null, $variables);
if ($colorValue !== null) {
$cssName = $varMap[$id] ?? '--e-gv-' . substr($id, 5);
$css[] = " {$cssName}: {$colorValue};";
}
}
$css[] = '}';
$css[] = '';
// 2. Generate class rules
foreach ($classes as $classData) {
$label = $classData['label'] ?? '';
$variants = $classData['variants'] ?? [];
if (empty($variants)) {
continue;
}
$selector = "{$selector_prefix} .{$label}";
foreach ($variants as $variant) {
$meta = $variant['meta'] ?? [];
$props = $variant['props'] ?? [];
$customCss = $variant['custom_css'] ?? null;
$breakpoint = $meta['breakpoint'] ?? 'desktop';
$state = $meta['state'] ?? null;
if (empty($props) && empty($customCss)) {
continue;
}
$declarations = [];
foreach ($props as $propName => $propDef) {
$decls = property_to_css($propName, $propDef, $variables, $varMap);
$declarations = array_merge($declarations, $decls);
}
if (!empty($customCss)) {
$declarations[] = $customCss;
}
if (empty($declarations)) {
continue;
}
$fullSelector = $selector;
if ($state !== null) {
$fullSelector .= ':' . $state;
}
if ($breakpoint === 'tablet') {
$css[] = "@media (max-width: 1024px) {";
$css[] = " {$fullSelector} {";
foreach ($declarations as $decl) {
$css[] = " {$decl}";
}
$css[] = ' }';
$css[] = '}';
} elseif ($breakpoint === 'phone') {
$css[] = "@media (max-width: 768px) {";
$css[] = " {$fullSelector} {";
foreach ($declarations as $decl) {
$css[] = " {$decl}";
}
$css[] = ' }';
$css[] = '}';
} else {
$css[] = "{$fullSelector} {";
foreach ($declarations as $decl) {
$css[] = " {$decl}";
}
$css[] = '}';
}
$css[] = '';
}
}
return implode("\n", $css);
}
/**
* Build a mapping from variable ID (e.g. "e-gv-cb68df0") to CSS custom property name (e.g. "--e-gv-dark").
*/
function build_variable_map(array $variables): array
{
$map = [];
foreach ($variables as $id => $var) {
$label = $var['label'] ?? substr($id, 5);
$map[$id] = '--e-gv-' . $label;
}
return $map;
}
/**
* Convert a single property definition to CSS declaration(s).
*
* @return string[] Array of CSS declarations like "color: #fff;"
*/
function property_to_css(string $propName, array $propDef, array $variables, array $varMap): array
{
$type = $propDef['$$type'] ?? null;
$value = $propDef['value'] ?? null;
return match ($type) {
'string', 'number' => [simple_declaration($propName, $value)],
'size' => size_to_css($propName, $value),
'color' => color_direct_to_css($propName, $value),
'global-color-variable' => color_variable_to_css($propName, $value, $varMap),
'dimensions' => dimensions_to_css($propName, $value),
'background' => background_to_css($propName, $value, $varMap),
'flex' => flex_to_css($propName, $value),
'layout-direction' => layout_direction_to_css($propName, $value),
default => [],
};
}
/**
* Resolve a value that might be a direct color or a global-color-variable reference.
*/
function resolve_color_value(mixed $value, array $variables): ?string
{
if ($value === null) {
return null;
}
if (is_string($value)) {
return $value;
}
$type = $value['$$type'] ?? null;
$val = $value['value'] ?? null;
if ($type === 'color' && is_string($val)) {
return $val;
}
if ($type === 'global-color-variable' && is_string($val)) {
return resolve_variable_ref($val, $variables);
}
return null;
}
/**
* Resolve a global-color-variable reference to its actual hex value.
*/
function resolve_variable_ref(string $varId, array $variables): ?string
{
if (isset($variables[$varId])) {
$var = $variables[$varId];
$inner = $var['value'] ?? null;
if ($inner && ($inner['$$type'] ?? null) === 'color') {
return $inner['value'] ?? null;
}
}
return null;
}
function simple_declaration(string $prop, mixed $value): string
{
return "{$prop}: {$value};";
}
function size_to_css(string $propName, mixed $value): array
{
if ($value === null) {
return [];
}
$size = $value['size'] ?? null;
$unit = $value['unit'] ?? 'px';
if ($size === null || $size === '') {
return [];
}
if ($unit === 'custom') {
// Already contains var() reference or similar
return ["{$propName}: {$size};"];
}
if ($unit === 'auto') {
return ["{$propName}: auto;"];
}
return ["{$propName}: {$size}{$unit};"];
}
function color_direct_to_css(string $propName, mixed $value): array
{
if ($value === null || !is_string($value)) {
return [];
}
return ["{$propName}: {$value};"];
}
function color_variable_to_css(string $propName, mixed $value, array $varMap): array
{
if ($value === null || !is_string($value)) {
return [];
}
$cssVarName = $varMap[$value] ?? null;
if ($cssVarName === null) {
return [];
}
return ["{$propName}: var({$cssVarName});"];
}
function dimensions_to_css(string $propName, mixed $value): array
{
if ($value === null || !is_array($value)) {
return [];
}
$declarations = [];
// Map logical properties to CSS shorthand or individual properties
if ($propName === 'margin' || $propName === 'padding') {
$blockStart = $value['block-start'] ?? null;
$blockEnd = $value['block-end'] ?? null;
$inlineStart = $value['inline-start'] ?? null;
$inlineEnd = $value['inline-end'] ?? null;
$values = [];
foreach ([$blockStart, $inlineEnd, $blockEnd, $inlineStart] as $side) {
if ($side === null) {
$values[] = '0';
} else {
$size = $side['value']['size'] ?? 0;
$unit = $side['value']['unit'] ?? 'px';
if ($unit === 'custom') {
$values[] = (string)$size;
} elseif ($unit === 'auto') {
$values[] = 'auto';
} else {
$values[] = "{$size}{$unit}";
}
}
}
// If all values are 0, skip
if (array_unique($values) === ['0']) {
return [];
}
return ["{$propName}: " . implode(' ', $values) . ';'];
}
// Generic dimensions — map each sub-property
foreach ($value as $subProp => $subValue) {
if ($subValue === null) {
continue;
}
$cssProp = "{$propName}-{$subProp}";
$size = $subValue['value']['size'] ?? null;
$unit = $subValue['value']['unit'] ?? 'px';
if ($size !== null && $size !== '') {
if ($unit === 'custom') {
$declarations[] = "{$cssProp}: {$size};";
} elseif ($unit === 'auto') {
$declarations[] = "{$cssProp}: auto;";
} else {
$declarations[] = "{$cssProp}: {$size}{$unit};";
}
}
}
return $declarations;
}
function background_to_css(string $propName, mixed $value, array $varMap): array
{
if ($value === null || !is_array($value)) {
return [];
}
$declarations = [];
// Background color
if (isset($value['color'])) {
$colorVal = $value['color'];
if ($colorVal !== null) {
if (($colorVal['$$type'] ?? null) === 'global-color-variable') {
$cssVarName = $colorVal['value'] ?? null;
$cssVarName = $varMap[$cssVarName] ?? null;
if ($cssVarName !== null) {
$declarations[] = "background-color: var({$cssVarName});";
}
} elseif (($colorVal['$$type'] ?? null) === 'color') {
$declarations[] = "background-color: {$colorVal['value']};";
}
}
}
// Background overlays (gradients, images)
if (isset($value['background-overlay'])) {
$overlay = $value['background-overlay'];
if (($overlay['$$type'] ?? null) === 'background-overlay' && is_array($overlay['value'] ?? null)) {
$layers = [];
foreach ($overlay['value'] as $layer) {
$layerType = $layer['type'] ?? null;
if ($layerType === 'color') {
$colorVal = $layer['color'] ?? null;
if ($colorVal) {
if (($colorVal['$$type'] ?? null) === 'global-color-variable') {
$cssVarName = $colorVal['value'] ?? null;
$layers[] = "var(" . ($varMap[$cssVarName] ?? $cssVarName) . ")";
} elseif (($colorVal['$$type'] ?? null) === 'color') {
$layers[] = $colorVal['value'];
}
}
} elseif ($layerType === 'gradient') {
$gradient = build_gradient($layer, $varMap);
if ($gradient) {
$layers[] = $gradient;
}
}
}
if (!empty($layers)) {
$declarations[] = "background: " . implode(', ', $layers) . ';';
}
}
}
return $declarations;
}
function build_gradient(array $layer, array $varMap): ?string
{
$angle = $layer['angle'] ?? 180;
$stops = $layer['stops'] ?? [];
if (empty($stops)) {
return null;
}
$stopStrings = [];
foreach ($stops as $stop) {
$color = $stop['color'] ?? null;
$offset = $stop['offset'] ?? 0;
if ($color === null) {
continue;
}
if (($color['$$type'] ?? null) === 'global-color-variable') {
$varId = $color['value'] ?? null;
$cssVarName = $varMap[$varId] ?? $varId;
$stopStrings[] = "var({$cssVarName}) {$offset}%";
} elseif (($color['$$type'] ?? null) === 'color') {
$stopStrings[] = "{$color['value']} {$offset}%";
}
}
if (empty($stopStrings)) {
return null;
}
return "linear-gradient({$angle}deg, " . implode(', ', $stopStrings) . ')';
}
function flex_to_css(string $propName, mixed $value): array
{
if ($value === null || !is_array($value)) {
return [];
}
$declarations = [];
$flexGrow = $value['flexGrow'] ?? null;
$flexShrink = $value['flexShrink'] ?? null;
$flexBasis = $value['flexBasis'] ?? null;
if ($flexGrow !== null && ($flexGrow['$$type'] ?? null) === 'number') {
$declarations[] = "flex-grow: {$flexGrow['value']};";
}
if ($flexShrink !== null && ($flexShrink['$$type'] ?? null) === 'number') {
$declarations[] = "flex-shrink: {$flexShrink['value']};";
}
if ($flexBasis !== null && ($flexBasis['$$type'] ?? null) === 'size') {
$sizeDecls = size_to_css('flex-basis', $flexBasis['value']);
$declarations = array_merge($declarations, $sizeDecls);
}
return $declarations;
}
function layout_direction_to_css(string $propName, mixed $value): array
{
if ($value === null || !is_array($value)) {
return [];
}
$declarations = [];
foreach ($value as $dir => $sizeDef) {
if ($sizeDef === null) {
continue;
}
$cssProp = match ($dir) {
'row' => 'row-gap',
'column' => 'column-gap',
default => "{$propName}-{$dir}",
};
$sizeDecls = size_to_css($cssProp, $sizeDef['value'] ?? null);
$declarations = array_merge($declarations, $sizeDecls);
}
return $declarations;
}
add_action('init', 'register_elementor_save_hook', null, 0);
function save_hook($post_id)
{
if (! Elementor\Plugin::instance()->db->is_built_with_elementor($post_id)) return;
generate_elementor_global_css_file('elementor.css');
}
function register_elementor_save_hook()
{
if (!defined('ELEMENTOR_VERSION')) return;
add_action('save_post', 'save_hook', 10, 1);
}
@hughc

hughc commented Jun 12, 2026

Copy link
Copy Markdown
Author

If you want to try this, read the hooks on the bottom. The script should not enqueue unless Elementor is active, and save_post hook should only run when saving an Elementor-enabled post.

You can change the generated css filename in the save_hook function.

To use it, you will need to
a) include_once() the above file, in your theme's functions.php and
b) enqueue the resulting stylesheet, alonside your theme's style.css

I cannot guarantee that the coverage of styles is 100% complete, but it has produced good results for me while this bug gets fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment