Skip to content

The Webflow rulebook

HTML rules

HTML is the structure of a section — its headings, text, images and buttons. These rules make that structure arrive in Webflow as real Webflow elements a designer can click on and change.

The rules

One section, no page shell

Checked automatically

A section is a single <section> element with its content inside. It never contains <!doctype>, <html>, <head> or <body>, and it is not wrapped in an extra div.

Why

The page already exists — HTFlow and Webflow both provide the html, head and body. A second set inside a section makes a broken page with a page inside it.

Think of it like

Delivering one new room to a house. You bring the room — you do not bring a second front door and a second roof with it.

Rejected

<html>
  <body>
    <section class="hero">…</section>
  </body>
</html>

Correct

<section class="hero">
  <h1 class="hero__title">…</h1>
</section>

Use the element that means the thing

Not auto-checked

A button is a <button>, a link is an <a>, a picture is an <img> with a real alt description, navigation is a <nav>. Not a <div> dressed up to look like one.

Why

Webflow has a native element for each of these, with its own settings (a Link Block has a URL field, an Image has an alt field). A div that only looks like a button arrives as a plain box with none of those settings.

Screen readers used by blind visitors, and search engines, also rely on the real element to understand the page.

Think of it like

Labelling moving boxes “kitchen” and “books”. Everything still arrives either way, but only the labelled boxes end up in the right room without anyone opening them.

Rejected

<div class="hero__cta">Get started</div>
<img src="team.jpg">

Correct

<a class="hero__cta" href="/signup">Get started</a>
<img src="team.jpg" alt="Our team at the studio">

Every styled element gets its own class, named after its section

Not auto-checked

Anything that is styled gets a class. Class names follow a pattern called BEM:

block — the section's name, e.g. pricing.
block__element — a part of it, e.g. pricing__card.
block--modifier — a special version, e.g. pricing__card--featured.

Why

In Webflow a class is shared across the whole project. If two sections both use a class called title, restyling one title restyles the other. Starting every class with the section's own name keeps each section's classes apart, and tells a designer at a glance where a class belongs.

Think of it like

A family surname. There may be many people called Sam in a school, but only one Sam Pricing. The block is the surname; the element is the first name.

html
<section class="pricing">
  <div class="pricing__grid">
    <article class="pricing__card pricing__card--featured">
      <h3 class="pricing__name">Pro</h3>
    </article>
  </div>
</section>

Styling and scripts never use an id. (Form inputs do carry an id — see Form rules — but only to connect a label to its field.)

No <ul> or <ol> lists

Checked automatically

Lists of things — features, links, steps — are built from plain divs laid out with flexbox or grid, not from <ul>/<ol> and <li>.

Why

Webflow's List element comes with its own built-in bullets, indents and spacing. Those defaults fight the section's own styling, and a designer ends up removing them by hand on every list. Plain boxes arrive with nothing to undo.

Think of it like

Moving your furniture into a room that is already furnished. You spend the first hour carrying the old furniture out. An empty room is quicker.

Rejected

<ul class="features__list">
  <li>Fast</li>
  <li>Compatible</li>
</ul>

Correct

<div class="features__list">
  <div class="features__item">Fast</div>
  <div class="features__item">Compatible</div>
</div>

No style="…" or onclick="…" in the markup

Checked automatically

HTML never carries a style attribute or an event attribute such as onclick, onload or onerror. Looks belong in the CSS, behaviour in the JavaScript. (JavaScript may still set a style while the page is running — that is allowed.)

Why

Webflow's Style panel works through classes. A style typed straight onto one element is invisible there: the designer changes the class and nothing happens, because the hidden inline style wins. Event attributes are dropped by Webflow, so a button relying on onclick silently stops working.

Think of it like

Writing a change on a sticky note stuck to one chair instead of on the room's plan. Whoever redecorates from the plan never sees it — until the chair refuses to match.

Rejected

<button style="color: red" onclick="openMenu()">Menu</button>

Correct

<button class="nav__toggle" data-ht-menu-toggle>Menu</button>

Everything the section needs is inside it

Checked automatically

No <link rel="stylesheet">, no <script src="…">, and no frameworks or libraries such as Tailwind, Bootstrap, jQuery, React or GSAP. The section's own CSS and JavaScript do the work.

Why

When a section is pasted into Webflow, only the section goes with it. A file loaded from somewhere else is left behind — or keeps loading from a server you do not control, which can change or disappear. Framework class names like Tailwind's mt-4 also mean nothing to Webflow's Style panel.

Think of it like

A recipe that says “add the sauce from my neighbour's fridge”. It works in your neighbour's kitchen. In anyone else's, the dish is missing its sauce.

Photos are real images, from Unsplash

Checked automatically

A photo is an <img> in the markup — even a full-width background photo, which is an <img> stretched behind the content with CSS. It is never a CSS background-image.

Images loaded from the web come from https://images.unsplash.com/.

Why

An <img> becomes a real Image element in Webflow. The designer clicks it and swaps the photo from the Assets panel — usually the first thing they want to do. A background image is buried inside a style, where it is much harder to find and replace.

Unsplash photos are free to use and stay online. A photo copied from a random website may be blocked, deleted later, or not licensed for your use.

Think of it like

A framed photo hung on the wall versus a photo printed into the wallpaper. Both look the same on day one. Only one can be swapped without redecorating.

html
<section class="hero">
  <img class="hero__photo"
       src="https://images.unsplash.com/photo-…"
       alt="A ceramic bowl on a wooden table">
  <div class="hero__content">…</div>
</section>
css
.hero { position: relative; }
.hero__photo {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Icons are SVG, never emoji

Checked automatically

Every icon is drawn with inline <svg> code. Emoji characters such as a check mark or a rocket do not appear in the markup at all.

Why

An emoji is drawn by the visitor's phone or computer, so it looks different on Apple, Android and Windows, and it cannot be recoloured to match your brand. An SVG looks identical everywhere, stays sharp at any size, and takes its colour from your CSS.

Think of it like

Asking each guest to bring their own drawing of your logo, versus handing everyone the same printed one.

Rejected

<span class="features__icon">✅</span>

Correct

<svg class="features__icon" viewBox="0 0 24 24" aria-hidden="true">
  <path d="M5 12l5 5L19 8" fill="none" stroke="currentColor" stroke-width="2"/>
</svg>

Anything a script uses gets a data-ht-* attribute

Not auto-checked

If JavaScript needs to find an element — a menu button, a tab, a slider — the element carries an attribute starting with data-ht-, such as data-ht-menu-toggle. The script looks for that attribute, never for the class. (The JavaScript side of this is checked automatically — see JavaScript rules.)

Why

Classes are the designer's to rename in Webflow. If the script found the button by its class, renaming the class would quietly break the menu. An attribute is left alone when classes change.

Think of it like

Finding a friend in a crowd by their name badge instead of their shirt. They can change their shirt; the badge still says who they are.

html
<button class="nav__toggle" data-ht-menu-toggle aria-expanded="false">Menu</button>