Just a quick overview of my Etch interface resting. As I get older, contrast becomes increasingly important, so my adjustments focus on it. I initially did some color changes, but the revised interface is much better in that respect. In my case, code is added to the wpCodeBox, and a location rule of URL contains “/?etch=magic”.
Updated Feb 3, 2026: I have since added a nice color coding option from Luke Wakefield and moved the template button to the top of the options in the left toolbar.
Updated April 13, 2026: I have added modified variables to improve the visibility of the new component interface.
Update April 16, 2026: Added styling for the mini-GUI. As well as Luke Wakefield’s “wrap in” update that highlights the loop, condition, and insert icons as well as the contextual menu.
Update March 25, 2026: Add larger, more colorful repeater and preview toggles.


The CSS
I just added this to wpCodeBox.
:root {
--e-base: hsl(240, 5%, 6%);
--e-foreground-color-muted: #ffffff;
--e-light: hsl(240, 7%, 99%);
--e-base-light: hsl(240, 4%, 17%);
--e-primary: oklch(85.19% 0.125 186.39);
--etch-neutral-dark: #202020;
--etch-opacity-high: 1;
--etch-opacity-medium: 0.9;
--etch-opacity-low: 0.8;
--etch-cps-color-family-category-label-opacity: var(--etch-opacity-medium);
--etch-font-size-m: 14px;
--e-font-size-m: 13px;
--etch-css-selector-pill-bg-active: color-mix(
in oklch,
var(--e-primary),
white 10%
);
--etch-css-qa-contextual-menu-bg: var(--e-base);
--wp-admin-theme-color: var(--primary-dark);
--etch-css-toolbar-icon-bg-hover: var(--e-base);
}
.etch-css-toolbar-toggle--active {
--etch-css-toolbar-icon: var(--e-base);
background: var(--e-primary) !important;
}
.right-click-menu__content {
--etch-panel-bg: color-mix(
in oklch,
var(--etch-css-qa-contextual-menu-bg),
white 10%
);
}
.etch-builder-accordion__item:has(
> .etch-builder-accordion__header--condition
) {
--e-structure-item-header-foreground-color: oklch(
from var(--e-condition) calc(l * 1.5) c h / 1
) !important;
}
.icon-wrapper:not(.etch-builder-accordion__quick-actions .icon-wrapper) svg {
color: #ffffff;
fill: #ffffff;
width: 100%;
height: 1.3rem;
}
.icon-wrapper:not(.etch-builder-accordion__quick-actions .icon-wrapper) {
border-radius: var(--radius);
padding: 6px;
}
.icon-wrapper:not(.etch-builder-accordion__quick-actions .icon-wrapper):hover {
background-color: var(--e-primary);
& svg {
fill: var(--e-base);
color: var(--e-base);
}
}
.etch-label {
font-size: 0.9rem !important;
}
.css-input-tabs {
& .css-input-tabs__trigger {
font-size: 12px !important;
}
& .css-input-tabs__wrapper {
grid-template-columns: repeat(auto-fit, minmax(15ch, 1fr)) !important;
}
}
.component-button.etch-builder-button {
justify-content: start;
}
.etch-css-toolbar-toggle {
--etch-css-toolbar-inactive-icon: 0.85 !important;
& svg {
width: 16px;
}
}
/* =========================================================
Etch Structure Panel: Colour-coded tag markers
========================================================= */
.etch-builder-structure-wrapper .etch-builder-accordion__header {
position: relative;
isolation: isolate;
}
.etch-builder-structure-wrapper .etch-builder-accordion__header::after {
content: '';
position: absolute;
left: 0;
top: 0px;
bottom: 0px;
width: 4px;
z-index: -1;
border-radius: 0;
background: var(--etch-tag-color, #64748b);
opacity: 1;
pointer-events: none;
}
.etch-builder-structure-wrapper
.etch-builder-accordion__header.etch-tag--selected {
box-shadow: inset 0 0 0 1px
color-mix(in oklch, var(--etch-tag-color, #64748b) 35%, transparent);
}
.etch-builder-structure-wrapper .etch-builder-accordion__header:hover {
background-color: color-mix(
in oklch,
var(--etch-tag-color, #64748b) 25%,
transparent
);
box-shadow: inset 0 0 0 1px
color-mix(in srgb, var(--etch-tag-color, #64748b) 35%, transparent);
}
.etch-builder-structure-wrapper
.etch-builder-accordion__header.etch-tag--selected {
background-color: color-mix(
in oklch,
var(--etch-tag-color, #64748b) 35%,
transparent
);
box-shadow: inset 0 0 0 1px
color-mix(in srgb, var(--etch-tag-color, #64748b) 45%, transparent);
}
/* move the template button to the top the ID can change base on what is instlled */
#bits-c7 {
order: -1;
}
.media-router .media-menu-item,
.media-frame-content,
.media-button {
color: black;
}
.etch-builder-icon-switch__thumb {
--etch-toggle-icon-size: 14px;
width: calc(var(--etch-toggle-icon-size) * 1.2) !important;
display: flex;
align-items: center;
justify-content: center;
padding-inline: 2px;
}
.etch-builder-icon-switch__root {
background: color-mix(in oklch, var(--e-primary) 70%, transparent);
--etch-toggle-width: 40px;
--etch-toggle-height: 20px;
&[data-state='checked'] {
background: var(--etch-toggle-bg-low-active) !important;
}
}
/* =========================================================
Luke's contextual menu update
========================================================= */
/* Element Menu (Bottom Bar) */
.element-menu > button[aria-label='Add loop block'],
.element-menu > button[aria-label='Add condition block'],
.element-menu > button:last-of-type {
--color-mix-perc: 30%;
}
.element-menu > button[aria-label='Add loop block']:hover,
.element-menu > button[aria-label='Add condition block']:hover,
.element-menu > button:last-of-type:hover {
--color-mix-perc: 40%;
}
.element-menu > button[aria-label='Add loop block'] {
background-color: color-mix(
in oklch,
var(--e-loop) var(--color-mix-perc),
transparent
) !important;
color: var(--e-loop);
}
.element-menu > button[aria-label='Add condition block'] {
background-color: color-mix(
in oklch,
var(--e-condition) var(--color-mix-perc),
transparent
) !important;
color: var(--e-condition);
}
.element-menu > button:last-of-type {
background-color: color-mix(
in oklch,
var(--e-primary) var(--color-mix-perc),
transparent
) !important;
color: var(--e-primary);
}
/* Right Click Menu */
div[textvalue='Wrap in Div'],
div[textvalue='Wrap in Condition'],
div[textvalue='Wrap in Loop'] {
--e-content-gap: 0.4em;
color: var(--right-click-menu-item-color);
}
div[textvalue='Wrap in Div'] .right-click-menu__item-label::before,
div[textvalue='Wrap in Condition'] .right-click-menu__item-label::before,
div[textvalue='Wrap in Loop'] .right-click-menu__item-label::before {
content: '';
width: 1em;
height: 1em;
display: inline-block;
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
mask-image: var(--right-click-menu-icon);
-webkit-mask-image: var(--right-click-menu-icon);
background: var(--right-click-menu-item-color);
}
div[textvalue='Wrap in Div'] {
--right-click-menu-item-color: var(--etch-text-color);
--right-click-menu-icon: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGFyaWEtaGlkZGVuPSJ0cnVlIiByb2xlPSJpbWciIGNsYXNzPSJldGNoLWljb24gaWNvbmlmeSBpY29uaWZ5LS1odWdlaWNvbnMtc3Ryb2tlIiB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik02IDNINEMyLjg5NTQzIDMgMiAzLjg5NTQzIDIgNVY3TTE3IDNIMTlDMjAuMTA0NiAzIDIxIDMuODk1NDMgMjEgNVY3TTIgMThWMjBDMiAyMS4xMDQ2IDIuODk1NDMgMjIgNCAyMkg2TTE3IDIySDE5QzIwLjEwNDYgMjIgMjEgMjEuMTA0NiAyMSAyMFYxOCIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD4KICAgIAkJCQk8cGF0aCBkPSJNOS41IDNIMTMuNU05LjUgMjJIMTMuNU0yMSAxMC41VjE0LjVNMiAxMC41TDIgMTQuNSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48L3N2Zz4=');
}
div[textvalue='Wrap in Condition'] {
--right-click-menu-item-color: var(--e-condition);
--right-click-menu-icon: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGFyaWEtaGlkZGVuPSJ0cnVlIiByb2xlPSJpbWciIGNsYXNzPSJldGNoLWljb24gaWNvbmlmeSBpY29uaWZ5LS1odWdlaWNvbnMtc3Ryb2tlIiB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xNSAxOS4wMDAyTDE1IDQuOTk5ODhMMTkgNy45OTk4OCIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD4KICAgIAkJCQk8cGF0aCBkPSJNOSA0Ljk5OTUxTDkgMTguOTk5OEw1IDE1Ljk5OTgiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBmaWxsPSJub25lIj48L3BhdGg+PC9zdmc+');
}
div[textvalue='Wrap in Loop'] {
--right-click-menu-item-color: var(--e-loop);
--right-click-menu-icon: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGFyaWEtaGlkZGVuPSJ0cnVlIiByb2xlPSJpbWciIGNsYXNzPSJldGNoLWljb24gaWNvbmlmeSBpY29uaWZ5LS1odWdlaWNvbnMtc3Ryb2tlIiB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMiAxMkMxMiAxMiA5LjI2MTQyIDE3IDYuNSAxN0MzLjczODU4IDE3IDIgMTQuNzYxNCAyIDEyQzIgOS4yMzg1OCAzLjczODU4IDcgNi41IDdDOS4yNjE0MiA3IDEyIDEyIDEyIDEyWk0xMiAxMkMxMiAxMiAxNC43Mzg2IDE3IDE3LjUgMTdDMjAuMjYxNCAxNyAyMiAxNC43NjE0IDIyIDEyQzIyIDkuMjM4NTggMjAuMjYxNCA3IDE3LjUgN0MxNC43Mzg2IDcgMTIgMTIgMTIgMTJaIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiPjwvcGF0aD48L3N2Zz4=');
}
CSSThe JS for color labels
(() => {
"use strict";
/* -----------------------------
Config
----------------------------- */
const PSEUDO_TAG_SELECTOR = ".etch-builder-accordion__header-button > span:not([class])";
const LABEL_SELECTOR = ".etch-builder-accordion__header-label";
const COMPONENT_COLOUR = "#6f4598";
const LOOP_COLOUR = "#5d6bc3";
const CONDITION_COLOUR = "#8b7332";
const TAG_GROUPS = {
section: "section",
div: "div",
header: "landmark",
main: "landmark",
footer: "landmark",
a: "link",
p: "text",
h1: "heading",
h2: "heading",
h3: "heading",
h4: "heading",
h5: "heading",
h6: "heading",
ul: "list",
ol: "list",
li: "list-item",
img: "media",
"etch:img": "media",
figure: "media",
video: "media",
svg: "media",
"etch:svg" : "media",
iframe: "embed",
};
const GROUP_COLOURS = {
// Structural
section: "#6434eb",
div: "#4a7dff",
landmark: "#3d4c73",
// Text
text: "#6c9ca1",
heading: "#6c9ca1",
// Interaction
link: "#28c95b",
// Lists
list: "#6c9ca1",
"list-item": "#6c9ca1",
// Media
media: "#f0980c",
// Embedded
embed: "#ff1744",
// Fallback
other: "#9e9e9e",
};
const WRAPPER_SELECTOR = ".etch-builder-structure-wrapper";
const HEADER_SELECTOR = ".etch-builder-accordion__header";
const TAG_SELECTOR = ".etch-builder-accordion__header-tag";
// Prevent double-observing the same root
const OBSERVED_ROOTS = new WeakSet();
/* -----------------------------
Helpers
----------------------------- */
function normaliseTag(text) {
return String(text || "").trim().toLowerCase();
}
function resolveGroup(tag) {
return TAG_GROUPS[tag] || "other";
}
function resolveColour(group) {
return GROUP_COLOURS[group] || GROUP_COLOURS.other;
}
function applyToHeader(headerEl) {
// -----------------------------
// 1) SPECIAL BUILDER NODES (OVERRIDES)
// -----------------------------
if (headerEl.classList.contains("etch-builder-accordion__header--component")) {
applySpecialType(headerEl, "component", COMPONENT_COLOUR);
return;
}
if (headerEl.classList.contains("etch-builder-accordion__header--loop")) {
applySpecialType(headerEl, "loop", LOOP_COLOUR);
return;
}
if (headerEl.classList.contains("etch-builder-accordion__header--condition")) {
applySpecialType(headerEl, "condition", CONDITION_COLOUR);
return;
}
// -----------------------------
// 2) NORMAL TAG RESOLUTION (your existing logic)
// -----------------------------
let tag = "";
const tagEl = headerEl.querySelector(TAG_SELECTOR);
if (tagEl) tag = normaliseTag(tagEl.textContent);
if (!tag) tag = normaliseBuilderTag(extractPseudoTag(headerEl));
if (!tag) {
const label = extractLabelText(headerEl);
if (label === "svg") tag = "svg";
if (label === "image") tag = "img";
}
if (!tag) return;
const group = resolveGroup(tag);
const colour = resolveColour(group);
headerEl.dataset.etchTag = tag;
headerEl.dataset.etchTagGroup = group;
headerEl.style.setProperty("--etch-tag-color", colour);
headerEl.classList.toggle(
"etch-tag--selected",
headerEl.classList.contains("etch-builder-accordion__header--selected")
);
}
function applyToAllIn(wrapperEl) {
wrapperEl.querySelectorAll(HEADER_SELECTOR).forEach(applyToHeader);
}
function extractPseudoTag(headerEl) {
// Looks for text like "<etch:svg>"
const spans = headerEl.querySelectorAll(PSEUDO_TAG_SELECTOR);
for (const span of spans) {
const txt = (span.textContent || "").trim();
if (!txt) continue;
// Normalise patterns like "<etch:svg>" or "<svg>"
const m = txt.match(/^<\s*([a-z0-9:_-]+)\s*>$/i);
if (!m) continue;
return normaliseTag(m[1]); // e.g. "etch:svg"
}
return "";
}
function extractLabelText(headerEl) {
const labelEl = headerEl.querySelector(LABEL_SELECTOR);
return normaliseTag(labelEl ? labelEl.textContent : "");
}
function normaliseBuilderTag(tag) {
// Map builder-prefixed tags to real tags
// etch:svg -> svg
if (!tag) return "";
if (tag === "etch:svg") return "svg";
if (tag === "etch:img") return "img";
return tag;
}
function applySpecialType(headerEl, type, colour) {
headerEl.dataset.etchTag = type;
headerEl.dataset.etchTagGroup = type;
headerEl.style.setProperty("--etch-tag-color", colour);
headerEl.classList.toggle(
"etch-tag--selected",
headerEl.classList.contains("etch-builder-accordion__header--selected")
);
}
/* -----------------------------
Shadow DOM traversal (open only)
----------------------------- */
function* iterOpenShadowRoots(rootDoc) {
// Walk all elements; yield any open shadowRoot we can access
const treeWalker = rootDoc.createTreeWalker(rootDoc.documentElement, NodeFilter.SHOW_ELEMENT);
let node = treeWalker.currentNode;
while (node) {
if (node.shadowRoot) yield node.shadowRoot;
node = treeWalker.nextNode();
}
}
function findWrappersInRoot(root) {
// root can be Document, ShadowRoot, or Element
if (!root || !root.querySelectorAll) return [];
return Array.from(root.querySelectorAll(WRAPPER_SELECTOR));
}
function findAllWrappers(doc) {
const wrappers = new Set();
// Main light DOM
findWrappersInRoot(doc).forEach((w) => wrappers.add(w));
// Open shadow roots
for (const sr of iterOpenShadowRoots(doc)) {
findWrappersInRoot(sr).forEach((w) => wrappers.add(w));
}
return Array.from(wrappers);
}
/* -----------------------------
Observers
----------------------------- */
function observeWrapper(wrapperEl) {
if (OBSERVED_ROOTS.has(wrapperEl)) return;
OBSERVED_ROOTS.add(wrapperEl);
// Initial pass
applyToAllIn(wrapperEl);
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.type === "childList" && m.addedNodes.length) {
m.addedNodes.forEach((node) => {
if (!(node instanceof Element)) return;
if (node.matches?.(HEADER_SELECTOR)) {
applyToHeader(node);
return;
}
node.querySelectorAll?.(HEADER_SELECTOR).forEach(applyToHeader);
});
}
if (m.type === "attributes") {
const t = m.target;
if (t instanceof Element && t.matches(HEADER_SELECTOR)) {
applyToHeader(t);
}
}
if (m.type === "characterData") {
const parent = m.target?.parentElement;
const header = parent?.closest?.(HEADER_SELECTOR);
if (header) applyToHeader(header);
}
}
});
obs.observe(wrapperEl, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ["class", "data-state", "aria-expanded"],
characterData: true,
});
}
function scanDocument(doc) {
// Find wrappers in doc + its open shadow roots
findAllWrappers(doc).forEach(observeWrapper);
// Also watch for wrappers appearing later (light DOM only)
if (!OBSERVED_ROOTS.has(doc)) {
OBSERVED_ROOTS.add(doc);
const docObs = new MutationObserver(() => {
findAllWrappers(doc).forEach(observeWrapper);
});
docObs.observe(doc.documentElement || doc.body, {
subtree: true,
childList: true,
});
}
}
function scanSameOriginIframes() {
const iframes = Array.from(document.querySelectorAll("iframe"));
for (const iframe of iframes) {
try {
const doc = iframe.contentDocument;
if (doc) scanDocument(doc);
} catch (e) {
// Cross-origin iframe: cannot access.
// If the builder is cross-origin, you will need to run this script in that iframe context instead.
}
}
}
function init() {
// Scan main document
scanDocument(document);
// Scan iframes now
scanSameOriginIframes();
// Keep rescanning occasionally (builders often mount/unmount panels)
window.setInterval(scanSameOriginIframes, 1000);
}
init();
document.addEventListener("DOMContentLoaded", init, { once: true });
})();
JavaScript
Leave a Reply