WordPress + Elementor Landing Page Builder
Build modern, SEO-focused landing pages that the user pastes into WordPress admin. The user has no FTP/hosting access — everything must be pasteable from the Elementor editor (HTML widget + Custom Code) or Customizer (Additional CSS).
Golden rules (these caused real breakage before)
<style>tags are mandatory in any file pasted into an Elementor HTML widget or Custom Code box. Raw CSS pasted there renders as visible text and breaks the page.- Raw CSS only (no
<style>) goes into Appearance → Customize → Additional CSS or Elementor Advanced → Custom CSS. Style tags there break it. - Font
@importmust NOT be inside<style>for paste files — use a<link rel="stylesheet">above the<style>block instead. Keep@importonly in the raw.csssource. - Scope everything to
.slr-landing(or the project's wrapper id/class) and use!importanton every rule to beat theme/Elementor styles. - Never restyle the global header/footer. The site already has Elementor
Theme Builder header/footer. Only reset the page content wrapper
(
main#content/ the landing HTML widget), never.e-con,.elementor-section, or.elementor-location-header/footerglobally. - No full-bleed
100vwbreakout — it misaligns content with the global header/footer container. Keep content in a.slr-container(max-width ~1200px).
Deliverables to produce
For each landing page, create these files in the project's templates folder:
| File | Purpose | Has <style>? |
|---|---|---|
<slug>.css |
Source CSS, scoped, @import at top |
No (raw) |
COPY-into-elementor-custom-code.html |
Paste whole file into Elementor Custom Code → Head | Yes |
<slug>-html-only.html |
One HTML widget: <style> + markup together |
Yes |
The user copies one finished file and replaces — do not make them hand-edit.
Workflow
- [ ] 1. Confirm slug, wrapper id (e.g. #slr-landing), keyword, location, brand colors
- [ ] 2. Write scoped <slug>.css (see structure below)
- [ ] 3. Write semantic HTML using <main id="..." class="..."> + .slr-container
- [ ] 4. Generate paste files with the script (style tags + <link> for font)
- [ ] 5. Give Elementor setup steps + "clear cache" reminder
Step 2 — CSS structure
- Top:
@import url('...Inter...')(raw file only). - Reset:
.slr-landing *, ::before, ::after { box-sizing: border-box !important; } - Hide duplicate theme title:
body:has(#WRAPPER) .page-header, .entry-header, h1.entry-title { display:none !important; } - CSS variables on
.slr-landing(colors, radius, shadow,--slr-max: 1200px). - Content container
.slr-container { max-width: var(--slr-max) !important; margin: auto !important; padding: 0 24px !important; } - Layout reset scoped to content only:
body:has(#WRAPPER) main#content.site-main,
body:has(#WRAPPER) main#content .page-content,
body:has(#WRAPPER) #content,
body:has(#WRAPPER) .site-content {
padding: 0 !important; margin: 0 !important; max-width: 100% !important;
}
body:has(#WRAPPER) main#content .elementor-widget-html .elementor-widget-container,
body:has(#WRAPPER) main#content .e-con-inner:has(#WRAPPER) {
padding: 0 !important; max-width: 100% !important;
}
- Responsive breakpoints:
1024px,768px,480px. - FAQ: use native
<details>/<summary>(no JS). - Gravity Forms: target
.slr-form-wrap .gform_wrapper, inputs,.gform_button.
Step 3 — HTML rules
- Root:
<main id="WRAPPER" class="WRAPPER" itemscope itemtype="https://schema.org/LocalBusiness">. - Every section wraps inner content in
<div class="slr-container">. - One
<h1>only (in hero). Use real image URLs from the WP media library. - Embed the form via shortcode inside the HTML:
[gravityform id="1" title="true"]. If a widget strips shortcodes, fall back to a separate Elementor Shortcode widget.
Step 4 — Generate paste files
Run the generator to wrap the raw CSS correctly (style tags + font link, no
@import inside <style>):
bash <skill_dir>/scripts/make-paste-files.sh <slug>.css <slug>-html-only.html
It writes COPY-into-elementor-custom-code.html (CSS only) and, if a
<main ...> HTML file is given, a combined *-html-only.html. See
scripts/make-paste-files.sh.
If you prefer to do it by hand, the paste file must look exactly like:
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" />
<style id="slr-landing-styles">
/* all CSS, all !important, no @import here */
</style>
Elementor setup steps (give these to the user every time)
- Edit page → Page Layout: Default (not Canvas/Elementor Full Width).
- Page Settings → Hide Title: Yes.
- Add a Section: Full Width, content width Full, padding 0.
- Paste content:
- Option A (simplest): paste
*-html-only.htmlinto one HTML widget. - Option B: paste
COPY-into-elementor-custom-code.htmlinto Elementor Custom Code → Head (this page only), and the markup into an HTML widget.
- Option A (simplest): paste
- Save → clear LiteSpeed / page cache before checking the live page.
Match file to field (pasting in the wrong field is the #1 break cause)
| Elementor field | Use file | Format |
|---|---|---|
| Custom Code box / HTML widget | COPY-into-elementor-custom-code.html or *-html-only.html |
with <style> |
| Advanced → Custom CSS / Customizer → Additional CSS | <slug>.css |
raw, no <style> |
CSS delivery format (also enforced by user rule)
When delivering CSS in chat, wrap it in <style> tags with a font <link> above
the block, all rules !important. Only give raw .css when the user explicitly
asks for an Additional-CSS-only file.
Additional resources
- For the project conventions, colors, and a full section checklist, see reference.md.