
Guide
Cursor rules for Elementor sites
Scope a rule to the theme paths that builds with widgets and global styles instead of hand-written markup, and treats generated CSS as read-only build output.
Published July 10, 2026
Scope a rule to the theme and plugin paths that tells the agent to build with widgets and the Site Settings kit rather than hand-written markup, and to treat Elementor's generated CSS and _elementor_data as build artefacts it may read but never write. The failure mode is not a broken page — it is a page your client can no longer edit.
The regression nobody notices for a month
Ask an agent to add a three-column feature section to an Elementor site and the shortest path is a block of HTML in a template file, or an HTML widget with a div soup inside it, plus a stylesheet to make it look right. The result renders correctly. It passes review. And it has quietly removed that section from the visual editor, so the next time a marketer wants to change a heading they file a ticket instead.
That is the whole risk with agents on page-builder sites, and it does not show up in any automated check. The page is not broken. It is less editable than it was, which is the one property the client paid Elementor for in the first place.
The rule file
Scope it by globs to the theme and plugin directories so it loads exactly when someone touches PHP, and state the alternative for every prohibition — a rule that only bans things leaves the model to improvise.
---
description: Editability rules for an Elementor site. Load when touching the
child theme, custom plugins, or anything under wp-content.
globs:
- "wp-content/themes/**/*.php"
- "wp-content/plugins/**/*.php"
- "wp-content/themes/**/*.css"
alwaysApply: false
---
# Elementor: keep the site editable
The client edits this site visually. Any markup that is not a widget is
markup they cannot change. Editability is a requirement, not a preference.
## Never write these
- **Do not hand-edit `wp-content/uploads/elementor/css/**`.** Those files
(`post-*.css`, `global.css`) are generated. Elementor > Tools >
Regenerate CSS & Data overwrites them, and your change vanishes.
- **Do not write to the `_elementor_data` postmeta by hand.** It is
slash-escaped JSON; a naive edit corrupts the layout and the page opens
empty in the editor.
- **Do not add layout HTML to PHP templates** to satisfy a design request.
It is invisible in the editor. Build it as a widget or a template part
the editor can see.
- **Do not use an HTML widget as a layout primitive.** One embed code is
fine. A three-column grid inside an HTML widget is a section the client
has lost.
- **No `!important`.** It wins against the editor's own controls, so the
client changes a colour in the UI and nothing happens.
## Do these instead
- Colours, fonts, and spacing scales go in **Site Settings > Global
Colors / Global Fonts** so they stay editable and stay consistent.
- Repeated blocks become **saved templates or global widgets**, not copied
sections.
- Genuine custom CSS goes in the child theme, enqueued after Elementor:
```php
add_action( 'elementor/frontend/after_enqueue_styles', function () {
wp_enqueue_style(
'child-overrides',
get_stylesheet_directory_uri() . '/css/overrides.css',
[],
filemtime( get_stylesheet_directory() . '/css/overrides.css' )
);
} );
```
- Custom functionality becomes a **registered Elementor widget** with
controls, so the client gets fields instead of hard-coded values.
## Performance still applies
Elementor is not an excuse for a slow site. This build holds 99 Performance
and 0 ms Total Blocking Time on mobile. Do not add a slider plugin, a second
icon library, or a font from a CDN to solve a layout problem.A widget instead of a template edit
The most useful thing the rule can do is show what “build it as a widget” actually looks like, because that is the step where an agent will otherwise reach for raw markup. A registered widget with controls turns every value the designer picked into a field the client can change.
<?php
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
class Site_Stat_Widget extends Widget_Base {
public function get_name() { return 'site_stat'; }
public function get_title() { return __( 'Stat', 'site' ); }
public function get_icon() { return 'eicon-number-field'; }
public function get_categories() { return [ 'general' ]; }
protected function register_controls() {
$this->start_controls_section( 'content', [
'label' => __( 'Content', 'site' ),
] );
// Every value the designer chose becomes a field the client owns.
$this->add_control( 'value', [
'label' => __( 'Value', 'site' ),
'type' => Controls_Manager::TEXT,
'default' => '99',
] );
$this->add_control( 'label', [
'label' => __( 'Label', 'site' ),
'type' => Controls_Manager::TEXT,
'default' => __( 'Performance', 'site' ),
] );
$this->end_controls_section();
}
protected function render() {
$s = $this->get_settings_for_display();
printf(
'<div class="site-stat"><span class="site-stat__value">%s</span>' .
'<span class="site-stat__label">%s</span></div>',
esc_html( $s['value'] ),
esc_html( $s['label'] )
);
}
}The gate
Cursor rules are context and cannot fail a commit, so the read-only paths need a hook. This one is short because the rule it enforces is absolute: generated output never belongs in a commit.
#!/usr/bin/env sh
# Elementor's generated CSS is build output. If it is staged, someone either
# edited it by hand or committed a regenerate. Both are wrong.
if git diff --cached --name-only | grep -qE '^wp-content/uploads/elementor/css/'; then
echo "✗ Generated Elementor CSS is staged (uploads/elementor/css/)."
echo " These files are rebuilt by Elementor > Tools > Regenerate CSS & Data."
echo " Put real overrides in the child theme instead."
exit 1
fi
# !important beats the editor's own controls, so the client's UI stops working.
if git diff --cached -- 'wp-content/themes/**/*.css' | grep -qE '^\+.*!important'; then
echo "✗ !important added to theme CSS — it overrides the Elementor controls"
echo " the client uses. Raise specificity instead, or use a Global Style."
exit 1
fiAdd wp-content/uploads/ to .gitignore as well, so the generated CSS cannot be staged in the first place — the hook is the backstop for the day someone force-adds it. The Claude Code version can deny writes to those paths outright rather than catching them at commit time, and if you are still choosing an editor for the site itself, Gutenberg versus Elementor covers that decision.
None of this rescues a build that started without a performance budget. That is set at the beginning, in how the site gets built, not enforced afterwards by a rules file.
Questions
Why does an AI editor break Elementor sites so easily?
Because the fastest way to satisfy most requests is to write HTML and CSS, and on an Elementor site that is exactly the wrong move. The markup an agent adds to a PHP template is invisible in the visual editor, so your marketing team cannot change it — the page looks finished and is quietly less editable than it was.
Is it ever right to edit _elementor_data directly?
For bulk operations across hundreds of pages, sometimes — but through WP-CLI against a database you have just backed up, never by hand. The field is JSON with slashes added by WordPress, so a naive read-modify-write corrupts the layout, and a corrupted _elementor_data means the page opens empty in the editor.
Where should custom CSS actually go?
Global colours and typography belong in the Site Settings kit so they stay editable. Genuine custom CSS belongs in a child theme stylesheet enqueued after Elementor's. What should never happen is editing the generated files under uploads/elementor/css — those are build output and get overwritten the moment anyone regenerates.
Related notes
- Claude Code rules for Elementor sites
- Gutenberg vs Elementor: which should you build on?
- Cursor rules for WCAG testing
More on web development
- Claude Code rules for WCAG testing
- Animating hero text without layout shift
- Never tween opacity on animated text
- next/font: why display optional beats swap
Related service: Web Development
Want this kind of engineering on your project?
Tall Karol takes on fractional and project-based engagements for startups and agencies.
Book a working session