Elementor has no “view source” button, which is the first thing people go looking for. What it has is an HTML widget: drop it on a page, paste markup, and Elementor outputs it verbatim. That covers most of why you’d want raw HTML in the first place.
The more useful question is the one that comes later: when should a block of HTML stop being HTML and become real Elementor elements? That’s where pages quietly rot, and it’s the part nobody writes about.
Viewing and editing HTML in Elementor
Adding raw markup. Search “HTML” in the widget panel, drag the HTML widget in, paste. It renders exactly what you give it, including <style> and <script> tags.
Seeing a page’s actual output. Elementor doesn’t show you its own generated markup in the editor. Use your browser’s inspector on the front end, or view source. What Elementor stores internally isn’t HTML at all, it’s a JSON tree in the _elementor_data post meta, and the HTML is generated from that at render time. That distinction explains a lot of otherwise confusing behaviour.
Editing an existing HTML widget. Click it, edit the content field. There’s no syntax highlighting, so for anything substantial, write it elsewhere and paste it in.
Custom CSS belongs in the Advanced tab (Elementor Pro) or Appearance → Customize → Additional CSS, not in a <style> tag inside an HTML widget, unless the styles are genuinely local to that one block.
What the HTML widget is legitimately good for
It has real uses, and “never use it” would be bad advice:
- Third-party embeds that ship as a script snippet: analytics, chat widgets, booking systems, maps.
- Structured data (JSON-LD) you want on one specific page.
- A truly one-off element that no widget covers and that you’ll never edit again.
- Prototyping, when you want something on the page now and will rebuild it properly later.
The pattern in that list: things that are opaque anyway. A booking script isn’t content you’d want to edit visually. It’s a black box, and a black box is exactly what the HTML widget is.
The trap
The trouble starts when the HTML widget holds content rather than an embed. A pricing table. A team grid. A menu.
It works. It looks right. And then:
- Nobody else can edit it. Your client asked for a page builder specifically so they wouldn’t need to touch code. A
<div>full of prices defeats the entire purchase. - It doesn’t respond to your design system. Change a global colour or font, and everything on the page updates except that block, because it never participated.
- Responsive behaviour is yours to hand-write. Elementor’s per-breakpoint controls don’t apply. You’re writing media queries by hand, in a text field, with no preview.
- AI agents can’t work with it either. This one is new but increasingly relevant. An agent can read and restructure native elements. Given a wall of markup, the best it can do is regenerate the whole blob.
None of these hurt on day one. All of them hurt in month six.
A real conversion, and what it cost
Recently we rebuilt a restaurant menu section that had been built exactly this way: a single HTML widget holding a tab bar and 28 menu rows, with inline JavaScript doing the filtering. It rendered fine.
Converting it to native Elementor meant an Elementor Pro Nested Tabs widget with six tab panels, each row a grid container holding heading widgets for number, name, description, and price. About 150 elements in total.
The result is editable by anyone, responds to global styles, and uses Elementor’s own responsive controls instead of hand-written media queries.
Two bugs surfaced during the conversion that are worth knowing about, because both are silent.
_css_classes does nothing on a container
Widgets and containers use different setting keys for the same concepts. The original section set _css_classes on a container, which is the widget key. Containers use css_classes, with no underscore.
Elementor accepted the value without complaint and stored it. It simply never rendered the class. The CSS targeting that class had never applied, on a live page, for months, and nothing anywhere reported a problem.
| Concept | On a widget | On a container |
|---|---|---|
| CSS classes | _css_classes | css_classes |
| Position | _position | position |
| Custom width | _element_custom_width | width |
This generalises past this one bug: Elementor stores any settings key you send. A wrong key looks like it worked. If a setting appears to have no effect, suspect the key before you suspect the value.
Grid columns and mobile don’t inherit the way you’d expect
The rows were CSS grid, 60px 1fr 1.2fr 100px on desktop, narrowing to two columns on mobile. We hid the description on mobile but forgot the row number, so the number took column one and squeezed the name into the 80px price column, wrapping the price onto its own line.
The lesson isn’t “remember to hide things”. It’s that a responsive grid needs its column count and its hidden children changed together. Change one without the other and the layout doesn’t break loudly, it just goes subtly wrong on a screen size you aren’t looking at.
When to convert, and when to leave it
A rough test: would anyone ever want to edit this without a developer?
| Leave it as HTML | Convert to native elements |
|---|---|
| Third-party embed scripts | Anything with words a client will change |
| JSON-LD structured data | Prices, names, dates, hours |
| One-off decorative flourish | Anything repeated more than three times |
| Something you’ll delete next month | Anything that should match global styles |
Prices are the giveaway. Prices change. If changing a price means editing markup, that section is in the wrong format.
Converting without doing it by hand
150 elements is a tedious afternoon of clicking. This is genuinely good work for an AI agent connected to a WordPress MCP server: you describe the structure, it builds the containers and widgets, and you check the result in the editor.
That’s how the menu above was rebuilt, and it’s why both bugs got found. Building it programmatically forces every setting to be explicit, and explicit settings are testable in a way that “it looks right” never is.
Related reading
- Widgets — how the widget catalog works
- Layout & structure — containers, grids, and nesting
- Elementor Addons — using addon-pack widgets instead of hand-rolled markup
- What is a WordPress MCP server? — the tooling behind the conversion
