CSS Tips and Tricks for Webflow

Webflow covers most of your CSS through its visual builder. If you want even more control ans style options, custom CSS gives you a lot more ways to alter how things look and behave.

In this blog, we’ll walk through some of the techniques I use regularly in Webflow projects; many of them simple, beginner-friendly additions that can make a big difference to your builds.

::before and ::after

These are called pseudo elements, their name comes from the fact that they do not exist in the HTML like normal elements but rather they are rendered by the browser. They used to create extra visual layers without adding more HTML elements.

<body>
<div>Text in a div</div>
  
</body>


<style>
div::before{
  content: "@";
}
div::after{
  content: "#";
}

  
</style>

In the above example the "@" will come before the text and the "#" will come after. The content: property tells the pseudo element what to display. It can be a string, an image url, or nothing. By default the pseudo elemets are inline, but we can override that with the position: property. Now we can give them any position value (like a normal element!), this opens the door to many uses.

Below is code for a custom bullet for a list:

<style>
  .rich-text ul {
  list-style: none;
  padding-left: 0;
}

.rich-text ul li {
  position: relative;
  padding-left: 1.6em;
}

.rich-text ul li::before {
  content: "⚡" ;
  position: absolute;
  left: 0;
  top: 0;
  font-size: 1em;
  line-height: 1;
}
</style>

The list-style: none, removes default list styles, and the content: "⚡" tells the ::before element to display that emoji. In this case we set position: absolute , so we need to make sure the "parent" element (.richtext ul li) is relative.

These elements can be used to make gradient overlays, custom underlines, gradient borders and so much more all without cluttering the DOM with non-essential cosmetic elements.


Due to the fact that they are not rendered in the DOM the same way normal elements are, they should be purely decorative, as screen readers cannot reliably read the content inside.

Smarter Targeting with Selectors

These selectors are super useful in Webflow when you need to style a parent based on the state of its child and when class stacking gets messy.

:is() Selector

The :is() selector lets you group multiple selectors together and apply the same styles to any element that matches any one of them:

<style>
  .richtext :is(h1,h2,h3){
    /* specific styles*/
  }
</style>

The list of selectors is forgiving, which means that even if one of the selectors passed to :is() is broken, the styles will still be applied to the rest of the selectors.

I find using :is() (especially when styling richtext elements) to be faster and cleaner then using the native Webflow styling elements when nested method. Also note that ::before and ::after cannot be passed into :is().

:not() Selector

Also called the negation class, it respresents the elements that do not match the list of selectors. For example you can use it to target all elements in a list except the first:

<style>
  
.li:not(:last-child){
  margin-bottom: 1rem;
}
  
</style>

It is also useful to set up CSS rules to make sure that you are not missing any content:

<style>
  html .wf-design-mode img:not([alt]){
    border: 2px solid orange;
  }
</style>

This is used to target all images that do not have alt text set and give them a red border. The class .wf-design-mode is a Webflow class which is applied when you are in the designer, but is removed when you preview and when you publish the site.

This is extremely useful, and can help make sure no alt text is missed and no links are left empty:

<style>

html.wf-design-mode a[href='#'] {
	border: 2px solid red;
}
  
</style>

It can also be used to prevent flash of unstyled content (FOUC) when applying animations to elements. You set the elements to visibility: hidden in CSS (For later use with GSAP autoAlpha) but visible in the designer:

<style>

  .prevent-fouc{
    visibility: hidden;
  }

  html .wf-design-mode .prevent-fouc{
    visibility: visible;
  }
  
</style>


This allows those elements to be visually seen in the designer while hidden on the live site.

:has() Selector

Normally in CSS elements are styled in a descending fashion: A child can be styled based on its parent, but you were not able to style ascendingly, a parent based on its child. With :has() you are now able to to just that!

<style>
  
.form-field:has(input:focus) {
  border-color: lightsteelblue;
}
  
</style>

We are styling the .form-field based on the state of its child. Of the 3 selectors :has() is definitly my most used. So many tiny interactions that once needed JavaScript can now be done using :has() as well as combining the other selectors.

Take for example the following code which is used to create a CSS accordion dropdown:

<style>

.accordion_content{
  display: grid;
  grid-template-rows: 0fr;
}
  
.accordion_wrap:has(.accordion_trigger:checked) .accordion_content {
	grid-template-rows: 1fr;
}


  
</style>

We use :has() to style the parent based on the state of the child. So when the checkbox (.accordion_trigger) becomes checked, now the accordion wrap gets grid-template-rows: 1fr. Using grid-template-rows is a nifty way to animate the height of an element from 0 to auto. To learn more you should check out this video by Kevin Powell.

There’s a lot more you can do with CSS, but these are some of the techniques I find myself using most often in Webflow builds. Hopefully you’ve picked up something useful here and keep exploring what’s possible with Webflow and CSS.