D
P
0
← All articles Baca dalam Bahasa Indonesia

Elementor: Under the Page Builder's Hood

Elementor's Heading Widget Strips `background-clip: text`, So Gradient Text Renders as a Solid Box

· · 8 min read
Elementor's Heading Widget Strips `background-clip: text`, So Gradient Text Renders as a Solid Box

There was a single gradient-filled word sitting at the centre of a radial diagram. The styling was the most ordinary kind: the gradient goes in as a background image, background-clip: text cuts that painting down to the shape of the glyphs, and the text fill is turned transparent through -webkit-text-fill-color so all that survives is a gradient in the shape of letters. The moment that word went into an Elementor heading widget, what appeared on screen was not gradient lettering but a solid coloured box.

The box is already half an explanation. When the clipping step runs, what is left on screen is letter shapes; when what is left is a rectangle the size of the element, the clipping never happened at all. So the thing to go looking for was not why the gradient had the wrong colour, but where the property that should have done the cutting went.

The title field does accept HTML, it is the style attribute that gets filtered

The easiest conclusion to land on here is that the heading widget's title field takes no HTML whatsoever. That is wrong, and the counter-example came from the same build. For numbered cards I wrapped the title text in <ol start="N" style="margin:0;padding-left:36px;list-style:decimal;"><li>...</li></ol> directly in the heading widget's title field, and it worked. The field really does accept HTML.

What actually happens is much narrower. The heading widget strips those inline styles out of its title HTML, while the text-editor widget passes inline styles through untouched. It is not the tag being rejected, it is the style attribute being filtered in transit.

I filed the finding as pitfall entry number 46 in the rebuild notes, written with the emphasis that the style is genuinely stripped by the heading widget and that a solid box is the result, closed off with a page marker and the date it happened. The word "stripped" is the part that matters. A note that only says "the gradient did not show up" would send me back through the CSS from scratch six months later.

This is also not the only time the heading widget imposes its own opinion on the content you hand it. Elsewhere on the same page, the heading widget enforces max-width strictly: with _element_custom_width set to 776, the text is genuinely capped at 776 and wraps to the next line, unlike a <div style="width:776px"> in an HTML widget, which still stretches to fit content that is wider. Same pattern, different property.

The fix, move the effect into a text-editor widget

Since the text-editor widget passes inline styles through as they are, the fix chosen was a text-editor widget holding a fully inline-styled p element for the gradient text-clip effect. Every declaration it needs sits inside one style attribute, from the font down to both clip properties:

<p style="margin:0;text-align:center;font-family:sans-serif;font-weight:500;font-size:24px;line-height:32px;background-image:linear-gradient(180deg,rgba(64,64,72,0.9),rgba(64,64,72,0.6));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;">Platform</p>

Note the two pairs deliberately written out twice in there. -webkit-background-clip pairs with background-clip, and -webkit-text-fill-color: transparent pairs with color: transparent. Inside the heading widget it is exactly those pairs that do not survive the trip to render.

The direction of this fix did not come from my own digging. The client went through several rounds of iteration on this section, and five critical fixes came back out of it. The second of the five is precisely this one: gradient text has to go through the text-editor widget because the heading widget strips background-clip: text. The first point is worth mentioning because it runs the other way, a rejection of something already in place. The client stated that there is no 290×290 outer decoration ring in that section, that the ring was wrongly added and is not in Figma.

The same bubble refuses two shadows at once

This small element still had a second obstacle in it. The Figma design puts two shadows on a single element, an 8px white halo plus a soft drop shadow, while Elementor only supports one box-shadow per element. In raw CSS the two are separated by a comma, but Elementor's control has no slot for the second one.

The way out is geometric rather than CSS. The halo becomes an extra container wrapped around the inner circle. The inner circle is 124 and the halo is 8 on each side, so the outer container has to be 124 plus 8 times 2, which is 140. The 140 container gets a white background and radius 999, and the surviving drop shadow is attached to it. That 16px of difference ringing the inner circle is what the eye reads as an 8px white halo.

The final structure looks like this, with the gradient text-editor widget sitting on the innermost layer:

container 140×140     z_index 3, white bg, radius 999, drop shadow 0 16 132 rgba(0,0,0,0.1)
  container 124×124   white bg, 1px #e5e7eb border, radius 999
    text-editor       the inline-styled gradient <p> from above

The spec itself files the halo trick as a related workaround from the same build, bundled together with the gradient text-clip fix, and writes the numbers out in short form in its code hints block. It makes sense that the two travel together, because both attach to the same element and both come from an Elementor control narrower than the CSS property behind it.

Element order in the array turned out not to be enough

The section this bug appeared in is not a simple one. It holds more than 19 absolutely positioned elements, and the ones named individually come to exactly 19: 6 connection lines, 12 tool icons and 1 centre text circle. They all sit in a parent container of 1008×395 with overflow: visible, and each of the twelve tool circles is 88×88 with overflow: hidden. One small detail that trips you up: the position field is named differently for widgets and containers, widgets use _position: absolute with a leading underscore while containers use position: absolute without one.

I had already followed the basic stacking rule. Element order inside the array determines the stack and later means higher, so the lines went in first, then the tool circles, then the centre circle last so the centre would cover the line crossings. The result was still wrong. Even with the DOM ordering correct, Elementor's container wrapping still produced overlap problems, and the lines were visible through both the tool circle icons and the centre text circle.

What settled it was an explicit tiered z_index on the diagram container's children, plus overflow: hidden on the tool circles so each icon is clipped neatly to the circle radius:

lines wrapper         z_index 1
12 tool circles       z_index 2 + overflow: hidden
centre wrapper 140    z_index 3

The 140 container from the previous section is the one carrying z_index: 3. So the wrapper that was born purely as a workaround for a white halo ends up doing double duty as the topmost layer of the diagram.

Position numbers inherited from old markup can already have drifted

One more finding from the same section, and this is the one most likely to slip past unnoticed. The initial build used positions lifted from the old HTML widget, and those numbers differ from Figma. One icon that sat at x=0 in the old HTML belongs at x=45 y=154, another that sat at x=920 belongs at x=875 y=153, and one icon recorded in the old HTML as 36×36 turns out to be one of three icons that Figma has at 64×36, wider. The conclusion is that the original HTML can drift from what Figma intended, so tool circle positions and inner image dimensions have to be cross-checked against the Figma metadata rather than merely hash matched.

That page was finally recorded as 100% done on 18 May 2026 with no HTML widgets left in it, and the work as a whole ended with zero HTML widgets across 16 of 16 pages. That zero is what made the small bug above unavoidable by the easiest route, because wrapping the gradient text in an HTML widget was simply not allowed. Under a list headed "DO NOT" sits the ban on using HTML widgets and the requirement that all sections be proper Elementor made of containers plus heading, text-editor, image and button widgets.

Lessons