Digital Marketing

This SaaS Collected 100k+ Leads Using Gated Content & Landing Pages

Taylor Loren knows it better than anyone: SaaS is a tough almond to milk.

It’s not just the complexity of building a valuable product that stands out from your competitors. The nature of the software as a service (SaaS) model is that your customers need to resubscribe every month (or year, depending). And no matter how great your platform is, that’ll inevitably mean churn.

For SaaS marketers, volume is the name of the game. You need to be constantly feeding more leads into the top of the funnel—leads that your team can nurture, demo, and convert.

We marketers know that acquisition is a whole lot easier when you create compelling content that gets people interested in your product. A must-read ebook or can’t-miss webinar (gated on a high-converting, on-brand landing page) is one of your best opportunities to gather more information about your visitors and start moving them down the funnel.

But even if you’ve got amazing, gateable resources at the ready, SaaS products eat up a ton of development resources, regularly leaving marketing teams in the lurch when it comes to front-end dev. Without the capacity to build landing pages and collect leads on your own, it can be a struggle to turn your hard-earned traffic into new subscriptions.

How Can Resource-Strapped SaaS Marketers Put Lead Gen Content to Work?

Just ask Taylor. After taking over as Head of Content Marketing at Later (a leading Instagram marketing platform), she and her small team managed to:

  • Independently create 26+ dedicated landing pages for each of Later’s content assets.
  • Generate 100,000+ leads with the intent to nurture new subscriptions for the platform.
  • Maintain an average conversion rate of roughly 60% across all of Later’s landing pages.
  • Lower the brand’s cost-per-acquisition through their Facebook and Google ads.

How did Taylor and her team pull it off? We got the team on the phone to find out.

Challenge: Building Landing Pages for Gated Content—Without a Developer

Taylor joined Later near the end of 2015. As the company’s first marketing hire, she made content a core pillar of Later’s acquisition strategy.

Later's squeeze page for their Instagram Influencer Marketing Strategy Guide

From the beginning, Taylor and her team focused on delivering value: deep-dive advice and comprehensive how-tos for executing an effective Instagram marketing strategy. With a steady stream of awesome content (plus promotion on social), Taylor grew monthly blog visitors from thirty thousand to over a million in just two years.

That’s a whole lotta peepers, no doubt—but Later needed a way to turn those visitors into leads.

Taylor knew that pointing blog and social traffic toward landing pages with gated content would help Later collect key information about their visitors and move them onto the nurture track. Like most SaaS businesses, though, they had a small marketing team and developer resources were mostly tied up in the platform. That meant Taylor’s crew had to create landing pages on their own.

We have a small creative team. We needed landing pages for each downloadable asset, but creating and executing on each one took a long time. […] We only had one designer on our team, and web design wasn’t our strongest skill set.

If Taylor was going to realize her vision for acquisition, she needed a fast and easy way to build landing pages—without any help from developers.

The Solution: A Drag-and-Drop Builder & Quick-Start Templates

Once Taylor’s team discovered a way to build landing pages without relying on developers, it was a whole new ball game.

Unbounce’s landing page templates have allowed us to execute a lot faster. We can create tens of landing pages quickly while also maintaining a high conversion rate.

This solution solved a couple of problems for Taylor right away. For one, Unbounce’s drag-and-drop builder and functional templates made it easy for her team to assemble gorgeous, on-brand landing pages in a jiffy. That means Taylor can now launch a dedicated page for every new resource her team puts together, like educational webinars and downloadable icon sets.

Free Instagram Stories Highlight Icons
Roughly 57% of all visitors are converting on this Later landing page for Instagram icons.

By directing visitors to landing pages through calls to action on popular blog posts and across social channels, Taylor and her team can learn more about their readers (like their industry and following size) and get them into the appropriate nurture track.

Here’s an example of how Later turns their blog visitors into leads using gated content:

A call to action from Later
This particular blog post includes a call to action for a product launch checklist.
A landing page to accompany the call to action
The corresponding landing page captures key information and is converting over 65% of all visitors.

Unbounce has also made life way easier for Later’s Lead Communication Designer, Chin Tan.

The best thing about Unbounce is that I can quickly see what’s working and what isn’t working, and tweak it on the spot instead of having to write another line of code or planning something completely different.

Love Later’s incredible landing page designs? Us too. Check out our huge library of landing page templates and see how easy it is to build something beautiful.

The Results: 100k+ New Leads and 60%+ Conversion Rates

With landing pages built in Unbounce, Later has collected more than 100,000 leads (and counting) from their gated resources. That probably has something to do with their conversion rates, which average around 60%—well over any industry benchmark.

And because landing pages have made Later’s Google and Facebook ads more effective, Taylor says that she’s reduced the cost of nabbing those leads, too.

Our designers have created a lot of ad-specific landing pages in Unbounce, which has helped us lower the cost-per-acquisition of new leads.

Check out this recent example of Later converting on their huge social following with a simple (but valuable) gated asset:

Later promotes downloadable calendar on Facebook
Later promoted a downloadable holiday calendar across their social channels, including Facebook.
A landing page for the holiday calendar
With tens of thousands of visitors, Later’s campaign landing page is converting at a rate of almost 65%.

That’s not all. Unbounce has given Taylor’s team way more insight into the effectiveness of their landing pages, helping them keep a pulse on their campaign performance.

The dashboard to see conversions is one of the biggest improvements we’ve seen since implementing Unbounce. Now, we can easily A/B test two pages and see at a glance which is performing better.

SaaS Brands Grow with Unbounce

In just a few short years, Taylor built a content ecosystem that established Later as an industry thought leader with a monster online following. By adding landing pages built with Unbounce, she kicked her content strategy into overdrive and collected tens of thousands of new leads for the SaaS platform.

(Wipe your mouths, fellow SaaS marketers. You’re drooling.)

If we’ve learned one thing from Taylor, it’s that having the freedom to execute on your marketing initiatives is key. Because they’ve removed the need for a front-end developer and taken control of the building process, Taylor and her team are able to launch new landing pages faster. That means every new piece of content they create is another opportunity to generate leads.

Want the same kind of results for your business? The first step is to produce some truly awesome content. Then you’re going to need a great landing page.

Creating Authentic Human Connections Within A Remote Team

Creating Authentic Human Connections Within A Remote Team

Creating Authentic Human Connections Within A Remote Team

Randy Tolentino

2019-08-12T13:00:59+02:00
2019-08-12T15:36:15+00:00

On any given day, walk into your local coffee shop and you’ll likely see someone situated at a table, staring into a computer screen. Without knowing any details, one thing’s for sure, it’s obvious they’re ‘at work’. Many of us have been there at some point in our careers—all we need is a power outlet, internet access, and we’re good to go.

As a software developer for a global company, I have the benefit of collaborating with people from all over the world. Here, from the IBM Design Studio in Austin, Texas, approximately 4,500 miles and at least a fifteen-hour flight separate myself from the nearest developers on our product team. If we consider the furthest members, try 18 hours away by plane, literally on the other side of the planet.

In this current role, I’m a part of a multi-site team where most of the technical people are based out of two primary locations: Cork, Ireland and Kassel, Germany. On this product team, I happen to be the only satellite developer based in Austin, although I do have the benefit of sitting alongside our design team.

Scenarios like these are common nowadays. In 2018, Owl Labs found that 56% of the participating companies in their study adopted or allowed for some form of remote arrangement for its employees. While this organizational approach has revolutionized the way we perform our job functions, it’s also paved the way for new patterns to emerge in the way we interact with each other across the distance.

A map of the world showing main locations where our teams are based out of

Our product dev team is spread across the globe. (Large preview)

Connecting With People

I’ve always found that the most fascinating aspect of a distributed team is its people. The ‘diversity of people’, in itself, deserves emphasis. In my view, this mix of skills, knowledge, and perspectives make up the heart of the experience. Being able to integrate with people from various backgrounds is eye-opening. Hearing different accents, discovering new ways to look at problems, and learning about world cultures all within the normal flow of the day is simply refreshing.

At the same time, one of the biggest hurdles for those on remote teams is forming a relationship with your colleagues, that genuine human connection. In a 2018 study by And Co and Remote Year, 30% of the respondents reported lack of community as the number one factor impacting their happiness at work, understandably so. Having virtual colleagues makes it easy to fall into the trap of thinking ‘we all have individual assignments, we only need to interact when our work crosses paths, and all we’re here to do is deliver’.

It’s just not enough.

Throughout my career, almost every project I’ve worked on involved others that were remote in some capacity. In this context, I’ve learned plenty about what it takes to build meaningful partnerships with people across varying distances and multiple time zones. My hope is that the following list of suggestions and ideas can help others out there who are navigating through the challenge of building actual human connections with their teammates.

  1. People, Not Resources
  2. Building A Continuous Improvement Culture
  3. Reading Emotions Across The Distance
  4. A Little Extra Effort Can Bridge The Gap
  5. Staying Thankful At The Core

1. People, Not Resources

Problem: Sometimes, remote team members can be mistakenly reduced or perceived as ‘contributors only’. In some cases, people are actually referred to as ‘resources’, literally.

About a year ago, I was on a kick-off call for a project where the client was headquartered in another city. At one point during the virtual meeting, I politely asked one of the client-stakeholders about credentials and ownership of a third-party app that they used. It was a fair question, I was simply gathering requirements. But his response towards me was belittling and unprofessional, making it seem as if I were questioning his knowledge or authority. From then on, it’s been a personal goal of mine to always acknowledge remote colleagues as people, not just resources.

At the very minimum, great collaborations are comprised of individuals who respect and care about one another on a holistic level. Sure, we collectively share the same objectives, but we’re also more than just workers. The very idea of ‘having a genuine connection with people you work with’, is a proven motivator for many when it comes to job satisfaction. It makes sense because as human beings, we have an innate need to connect with one another—this is especially true on remote teams.

These are some ways to remind us that people are the foundation of your team:

  • Proactively introduce yourself to as many teammates as possible.
  • Learn about each other, share cultures, stories, and professional backgrounds.
  • Be mindful of your audible and legible tone (on calls and chats), keep it friendly and respectful.

2. Building A Continuous Improvement Culture

Problem: As remote team members, we can find ourselves stranded on an island if we don’t ask for help sooner than later.

Oftentimes, we make the mistake of believing that in order to bring value to the team, we must know everything (all the time). This ‘rugged individualist’ mentality is a one-way ticket to imposter syndrome. The next thing you know, a significant amount of time passes, your progress remains stagnant, and by the time someone extends a hand you’re already underwater. Remember, no one knows everything, and more importantly, teams thrive off of collaboration. We learn together.

The best functioning teams that I’ve been on all had a healthy continuous learning culture. On these teams, failure is okay, especially when our mistakes are transformed into learning opportunities. When working on a remote team, instead of running or hiding from our mistakes, I personally recommend that we fail in “public”. This lets the team know that you hired a human, one who’ll run into stumbling blocks and will inevitably produce errors. By doing so, this gives others the opportunity to either offer assistance, or learn.

A GitHub pull request including a comment seeking for help and clarity

Asking for help and admitting mistakes allow you to improve your craft. (Large preview)

You can contribute to the team’s improvement culture in the following ways:

  • Leverage public channels to show when you’re stuck, this allows the group to help or point you in the right direction.
  • Share what you’ve learned in public channels, retrospectives, or through documentation.
  • Spend time listening and scanning for ways to help others.
  • When you do help your team, remind them that everyone asks for help and how you are all on this journey together.

3. Reading Emotions Across The Distance

Problem: Understanding someone’s emotional state is already difficult when you’re in the same office. When it comes to communicating online, getting a good read on someone’s tone, feelings, or intent, becomes even more challenging.

In person, it’s relatively easier to exercise soft-skills because you’re in the same physical space as your colleagues. From laughter to frustration, there’s an advantage we gain from being co-located when it comes to interpreting how someone is feeling. We can gauge these emotions based off of vocal inflections, facial expressions, and gestures.

However, when we’re far from teammates, we have to be more creative when trying to convey or pick-up on these sentiments. When I breakdown how I communicate with my team throughout the day, about 90% of it occurs in chats; the remaining 10% is split between in conference calls, email, and any other tool that allows for commenting. In each of these modes, we have to clearly convey not only what we say, but what we mean and how we feel.

A conversation in Slack showing how emoji helps convey feelings

Using the appropriate emoji can allow others to have a better grasp of how you might feel. (Large preview)

We can improve our team’s collective ability to read and convey emotions in the following ways:

  • Video calls provide a visual and audible opportunity to pick up on our expressions; turn on the camera and smile at each other.
  • Instead of just focusing on business objectives, develop the habit of paying particular attention to tone and feelings when communicating with your team.
  • Use the appropriate emoji to supplement your thoughts or reactions; these fun and effective special characters can help to surface your feelings.

4. A Little Extra Effort Can Bridge The Gap

Problem: The physical mileage between team members and multiple time-zones can cause a strain in our effort to connect with our colleagues.

With Germany being 7 hours ahead and Ireland being 6, I am constantly reminded how the time difference is an issue. On most occasions, when I have questions or run into some sort of blocker anytime after lunch, all of our dev team is offline.

If I worked the typical 9-to-5 schedule, I’d only have about 3 to 4 hours of an overlap with my remote team. It took me a few weeks to fully realize how much the time difference was a factor, but once I did, I decided to flex my schedule.

When I learned that our Ireland team had daily standups at 10:30 AM, (4:30 AM our time), I asked to be invited to those meetings. Most people might think: that’s way too early! But, for a couple of weeks, I joined their call and found it to be incredibly helpful from an alignment and tracking perspective. More importantly, the team understood that I was here to be as helpful as possible and was willing to sacrifice my own conveniences for the sake of the team.

While we can’t do much about the distance, there are a few strategies to potentially improving the overlap:

  • Find creative ways to extend yourself for the interest of the team; these gestures show good faith and the willingness to make things better for the group.
  • Be equally accommodating towards others and reciprocate the flexibility your colleagues show towards one another.
  • Take the most advantage of the overlapping time, ask critical questions, and ensure no one is blocked from making progress.

5. Staying Thankful At The Core

Problem: In our work, we spend almost every minute of every day focusing our attention on solving some sort of problem.

Deeply embedded into my personal culture is an appreciation mindset; practicing gratitude allows me to maintain a fairly good mood throughout the day. I regularly think about how blessed I am to work in the tech industry, helping to solve some of the world’s most challenging problems. I can never take this for granted. Being able to listen to my favorite hip hop playlists, writing code all day, and having access to learning from a wealth of individuals, this is a dream come true. This thankful mentality stays with me no matter what and it’s something I aim to emit when interacting with my team.

It’s not always easy though. In the tech industry, we spend nearly every minute of the day, using our skills and creativity to find our way out of a problem. Sometimes we’re focused on business problems, maybe we’re solving a user pain point, or maybe we’re managing an internal team issue. This repetition, over time, this can take a toll on us, and we could forget why we even chose to do this.

Keeping a positive attitude can help lift team morale and has been known to make you a better collaborator. Even though you may be far from your teammates, your attitude comes through in your work and communications. Here are some pointers to keep in mind when showing appreciation towards your team:

  • Use company tools to acknowledge a teammate.
  • Ask teammates how they’d like to be recognized, and thank them accordingly.
  • Relay positive feedback to a colleague’s lead or manager.

A thank you note using the digital recognition tool at IBM

At IBM, we use the Recognition platform to acknowledge our peers. (Large preview)

Remember To Be Human

You see them on various social media platforms, posts or photos of a team retreat where employees step away from their projects for a few days to focus on team-building. Some organizations intentionally design these events into their annual schedules; it’s an excellent way to bridge the gaps and facilitate bonding. Many teams return home from these retreats and experience improved alignment and productivity.

For other organizations, having the ability to meet face-to-face with your remote counterparts isn’t an option. In these cases, we have to make the best of our circumstances by depending on our soft-skills and creativity to help form the alliances we need. I’m confident that by caring for one another as people first, we collectively become better at what we do.

Behind every @username, profile picture, and direct message is a person, one who probably cries and rejoices for the same reasons you might. As technology continues to influence new social behaviors that shape the way we work, it’s important to remember that phenomenal teams are composed of individuals who understand each other and care for one another.

People make products, it’s not the other way around. No matter how far the distance between you and your teammates, I encourage you to make a conscious effort to connect with one another, invest in long-lasting friendships, and last but not least, remember to be human.

?

Smashing Editorial
(ra, il)

Optional Chaining

For all of the improvements that the JavaScript language has added over the past few years, like the spread operator, default argument values, and arrow functions, there are still a few features I’d love to see implemented. One such feature is optional chaining. Optional chaining allows developers to reference object properties which may or may not exist without trigger an error.

Take the following example case:

const person = 
  name: "David",
  skills: 
    javascript: 
      frameworks: ["MooTools", "React"],
    
  ,
  save: () =>  
;

// Property that *doesn't* exist (css)
person.skills.css.frameworks;
// Uncaught TypeError: Cannot read property 'frameworks' of undefined

Attempting to get a property of an undefined parent results in a TypeError which can brick your application. In this case we’d want to check to ensure that css property exists:

if(
  person.skills && 
  person.skills.css && 
  person.skills.css.frameworks) 
    // ...

I wrote a get and set utility called Objectifier to make referencing nested object properties easier, but with the Optional Chaining proposal, we now have a native way.

A simple example of optional chaining is:

const skills = person?.skills;

You can continue the pattern down the line of nested objects:

const frameworks = person?.skills?.javascript?.frameworks;

If ever a property doesn’t exist, the chaining stops and undefined is returned. Optional chaining also supports bracket syntax:

const language = "javascript";
const frameworks = person?.skills?.[language]?.frameworks;

You can also call a function without penalty:

// Calls save if exists, otherwise nothing
const frameworks = person?.save();

You can even use the chaining syntax on the top level object:

addEventListener?.("click", e =>  );

methodDoesntExist?.(); // undefined

You can even use destructuring with optional chaining:

const  frameworks, doesntExist  = person.skills?.javascript;

At the time of writing, optional chaining doesn’t appear in any browsers yet, but you can play around with optional chaining at the Babel online compiler.

Optional chaining seems like a somewhat controversial change. It’s been argued that developers should know and validate the objects they’re using; on the other hand, the continuous nested property checking is a nightmare. I look forward to optional chaining in JavaScript. What are your thoughts?

The post Optional Chaining appeared first on David Walsh Blog.

15 High-Converting Landing Pages (That’ll Make You Wish You Built ‘Em)

Don’t get us wrong: we love good-looking landing pages. The way the colors contrast to draw attention; the striking custom photography and animation; the elegant application of negative space and rule-of-three layouts. Seriously, these things keep us up at night.

But here at Unbounce, we know that there’s more to a landing page than looks. We want the kind of page that won’t embarrass you when you bring it home to your CMO. One that you can really, you know… build a campaign with.

What we really want is a landing page that converts.

What Makes a High-Converting Landing Page?

(“Yeah, yeah, take me to the high-converting landing page examples!”)

People have created a lot of landing pages with the Unbounce Builder (like, so many, you guys), so we think we’ve got a pretty good understanding of what makes a page convert. Over the years, it’s become clear that nearly all successful landing pages have some key elements in common.

High-converting landing pages:

    • Have a strong, contextual hero shot and supporting imagery
      Your hero shot (the primary image or video on your landing page above the fold) is the first thing visitors are going to focus on, so you’d better make it captivating. Show your product or service in the context of use: demonstrate how it works and make it easy for people to visualize themselves enjoying the benefits.
    • Present a single and focused call to action
      Your call to action (CTA) is the one thing you want visitors to do on your page and your primary conversion metric. Make sure your CTA is obvious (from a design perspective) and compelling (from a copy perspective). Best practice is generally to remove any secondary links that might cause someone to leave your page before converting through your CTA, including site navigation.
    • Clearly state your value proposition with a compelling header and subhead
      Why should visitors accept your call to action? Use your headline and subheadline to articulate your value proposition, clearly stating the benefits of your offer and what makes you different from your competitors.
    • Outline the features and benefits (with emphasis on the latter)
      Sure, people need to know what your product or service does, but they’re much more likely to convert if they understand the benefits they’ll receive by following through with your CTA. Benefits-oriented messaging (as we’ll see in some examples) is one of the best ways to drive conversions.
    • Include testimonials and other forms of social proof
      People are much more likely to convert on your landing page if they believe that others have done it before them and have been happy with the results. Social proof—testimonials, reviews, partner logos—can be a fast and effective way to build credibility with your prospects. (What’s the difference between a prospect and a lead, anyway?)

Has your page got all the elements you need to drive conversions? The Unbounce Landing Page Analyzer grades your page on nine performance metrics and calls out opportunities to increase your conversion rate.

High-Converting Landing Page Examples

Before we dive into our high-converting landing pages examples, let’s set some ground rules. All of the pages featured below have had at least 500 visitors on the low end, though many have had more than 100,000. They’re also all converting at a rate of at least 30%. (For reference, the average landing conversion rate sits somewhere around 4%.)

It’s worth noting that conversion rate is influenced by many factors outside the actual content of your landing page. For example, we know that the average conversion rate varies widely depending on your industry. Be sure to check out our Conversion Benchmark Report to see how you stack up against your competitors.

There’s also the question of traffic quality: if your page is getting a lot of traffic from poorly-targeted ads, your conversion rate is going to be lower than it would be with more qualified visitors. And, of course, click-through pages are going to convert higher than lead gen pages because the conversion goal is much simpler. Keep these things in mind before judging your own pages too harshly.

With that disclaimer out of the way, here are 15 high-converting landing page examples from Unbounce customers (with conversion tips from the people who actually built them).

1. Promo

Industry: Social Media / Conversion Rate: 46.94%

High-Converting Landing Page: Promo
Image courtesy of Promo. (Click image to see the full page.)

Promo’s high-converting hint: Use video to increase visitor engagement and drive conversions.

If we’ve said it once, we’ve said it at least several more times: using video on your landing page is a great way to boost engagement and crank up your conversion rate. In fact, including some moving pictures on your page can increase conversions by as much as 80%. A worthwhile investment, no?

Promo thought so, too, which is why they included a ton of video content on this landing page for their video creation service—from the header, to the explainer video, to the sample videos that visitors can actually use in their own marketing.

Noted Yael Miriam Klass, Content Lead at Promo:

We specialize in creating converting videos that attract viewers and elicit action.

To that end, our landing page has a beautiful and dynamic header video taking up the first fold, overlaid with text that shows a clear value proposition.

Still, video is just part of the equation. You want visitors to convert, and that means getting them to follow through with your call to action. Don’t worry—Yael’s on it: “No landing page can make an impact without direct text and an eye-popping CTA button on the first fold.” Promo nailed those elements, then topped it all off with a swack of testimonials and strong client logos. Great job.

2. edX

Industry: Education / Conversion Rate: 52.68%

High-Converting Landing Page: edX
Image courtesy of edX. (Click image to see the full page.)

edX’s high-converting hint: Simplify your pitch and make the benefits crystal clear.

Us marketers tend to be so close to our products and services that we can sometimes overload prospects with too much information. “Yes, our core offering is X, but how ’bout these bells? What about them whistles?” No, they probably didn’t know about those extra benefits—but at this stage, they probably didn’t need to.

On the landing pages for their online courses, edX’s Senior Growth Marketer Josh Grossman chose to pare the message down to just the main points he wanted to visitors to take away. “Rather than get bogged down in the details of the course, we made it easy for people to understand what they’ll learn using just a few bullet points.” That, and an unambiguous head and subhead followed by solid social proof.

“In our testing, shorter copy worked better than longer copy,” Josh added. “Either you want to learn Python, or you don’t.”

That’s an insight we should all take to heart. Some people aren’t going to want what you’ve got, no matter how much extra information you throw at them. Better to save your breath (or word count) and focus on the people who do.

3. Simply Business

Industry: Insurance / Conversion Rate: 62.26%

High-Converting Landing Page: Simply Business
Image courtesy of Simply Business. (Click image to see the full page.)

Simply Business’s high-converting hint: Present complicated products in an uncomplicated way.

Insurance has always been a complex product. Between liabilities, deductibles, prohibited risks, and loads of other terms we had to Google, just signing up can feel like a crash course in law. And by the time you’re covered, you still might not understand what being covered even really means.

Simply Business wants to change that, and it lives up to its name with this landing page that makes business insurance feel, well, simple.

Rather than risk overwhelming visitors with a ton of information about their policies, Simply Business keeps things light. The headline immediately soothes some of the most common concerns about insurance—that it’s complicated, that it’s expensive—and the bulleted how-to instructions make signup feel like a breeze.

It’s only after visitors click through the call to action that Simply Business introduces some friction in a multi-step form—but by then, visitors have already overcome that first mental hurdle and are much more likely to see it through.

4. Later

Industry: Social Media / Conversion Rate: 57.92%

High-Converting Landing Page: Later
Image courtesy of Later. (Click image to see the full page.)

Later’s high-converting hint: Maintain conversion scent and balance your incentives.

Humans are fickle creatures. They’re easily distracted. They get confused. Mostly, they’re bad. As a marketer, that means you often need to hold their hands—or, for our purposes, hold their noses—through each step of the purchase process.

Conversion scent is the principle of keeping written and visual cues consistent all through the consumer journey. That’s what Later did for this lead generation campaign, as Chin Tan, the company’s Communication Design Lead, explains:

We maintained conversion scent throughout the campaign. The offer matches what’s in the ad, in the email, in the creative before the landing page, and after the page as well.

Later - Conversion Scent

Check out Later’s clever use of conversion scent to deliver a unified customer journey.

Chin also acknowledges that the simplicity of the offer contributed to the page’s success. “It’s clear right away what you’re getting: you’re exchanging your email for access to the guide. The form isn’t too long and only requests pertinent information.” Asking for too many personal details at this top stage of the funnel can spook visitors. Make sure your ask matches the value of the incentive you’re offering.

5. The Listings Lab

Industry: Real Estate

High-Converting Landing Page: The Listings Lab
Image courtesy of The Listings Lab. (Click image to see the full page.)

The Listings Lab’s high-converting hint: Use straightforward design and focus on the offer.

Another lead generation page, our example from The Listings Lab isn’t the flashiest on the list, but don’t let that fool you: this simple page packs a punch.

First, let’s talk design. The Listings Lab has done a great job of condensing all of the page content into a small space without making anything feel crowded. Visitors don’t need to scroll to understand what’s on offer and why it’s valuable.

“A mock-up of the download helps people feel that it’s a well-produced, real thing that they can read,” offered Yves Lenouvel, Marketing Director at The Listings Lab. “Bold text on the form’s big, colorful button draws people’s attention to the CTA.” Not to mention the directional cue, which is another nice touch.

Still, it’s the benefits-oriented copy that puts this page over the top. The Listings Lab really zeroes in on key pain points for realtors—cold calling, poor leads, long hours—and offers an alternative. “The first piece of copy people see is speaking to the visitors’ pain and then presenting them with a solution.” Read the guide, make more money, get your life back. What’s not to like?

Bonus points for a privacy statement that instills confidence while keepin’ it casual.

6. Twillory

Industry: Clothing / Conversion Rate: 46.85%

High-Converting Landing Page: Twillory
Image courtesy of Twillory. (Click image to see the full page.)

Twillory’s high-converting hint: Build custom experiences for your mobile visitors.

We don’t need to tell you that mobile consumers should be a priority. (Although we have been telling you for, like, ever.) By 2017, mobile had become the dominant source of web traffic worldwide at 50.3%—a segment that expanded last year, reaching 52.2%. It’s no longer enough to think of mobile consumers as part of your online audience. In 2019, they’re often the majority. (Check those GA reports, people.)

Aditya Bagri, Digital Automation Manager at Agency Within, described how his outfit is adjusting to a world in which consumers’ first experience with a brand is often on their phones:

Our landing page creation strategy is mobile-first, and optimizing for mobile helps us get first-time viewers down the funnel.

Better than merely building mobile-responsive pages, many brands are creating separate experiences for their mobile visitors.

High-Converting Landing Page: Twillory Mobile
Image courtesy of Twillory. (Click image to see the full page.)

Enter Agency Within and Twillory. On desktop, this landing page includes videos and GIFs—elements that have been shown to increase visitor engagement and help drive conversions. On mobile, though, we get a stripped-down version that maintains the visual appeal of its big brother while also ensuring lightning-fast load times on cellular connections.

And Twillory gets an extra nod for using an Unbounce popup to give visitors additional conversion incentives.

High-Converting Landing Page: Twillory Popup

Has your page got all the elements you need to drive conversions? The Unbounce Landing Page Analyzer grades your page on nine performance metrics and calls out opportunities to increase your conversion rate.

7. TyresOnTheDrive

Industry: Automotive

High-Converting Landing Page: TyresOnTheDrive
Image courtesy of TyresOnTheDrive. (Click image to see the full page.)

TyresOnTheDrive’s high-converting hint: Be clear in your headline and then back it up with social proof.

When it comes to landing page copy, clarity leads to conversions. Your visitors should know within seconds exactly what you’re offering and why they need to care. If they don’t, they’re likely to bounce.

This page from TyresOnTheDrive illustrates the importance of clarity with a headline that immediately conveys the value proposition: “Expert Tyre Fitting At Your Home or Work.” Right away, we know the differentiator is that we don’t have to go to a mechanic—they’re coming to us. Coupled with a quick how-to, a load of testimonials, and a big-brand logo collage, we have enough information about TyresOnTheDrive to make a purchase decision in a very short period of time.

The result? Conversions through the roof.

But great conversion rates aren’t an excuse to stop testing. Chris Wood, TyresOnTheDrive’s Senior UX Designer, described how the company has played with other pitch angles yet keeps coming back to the fundamentals. “We’re finding that more benefit-oriented messaging seems to convert better than pushing offers and promotions.”

8. ooba

Industry: Finance / Conversion Rate: 35.57%

High-Converting Landing Page: ooba
Image courtesy of ooba. (Click image to see the full page.)

ooba’s high-converting hint: Use a descriptive call to action that tells visitors what’ll happen next.

Yes, it’s important that your visitors know what you’re offering the moment they hit your page. But just as essential is that visitors know what you want them to do—and what will happen when they do it.

This page for ooba (designed by digital agency Signpost) provides a great example of an effective call to action. At a glance, the copy—along with the contextual cues and supporting information—tells us what we can expect when we fill out the form.

“The form is positioned at the top of the page, above the fold, which makes the action we want the user to take clear from the outset,” said Adam Lange, CEO at Signpost. “The contrasting color draws the user’s attention to the end goal, and the descriptive button confirms the action they’re about to take.”

The form asks for a lot of information, but that might actually help build credibility in this context—we’re trying to get a home loan, not sign up for a newsletter. It makes sense that we’d need to provide some details if we’re expecting to be pre-qualified.

9. ClaimCompass

Industry: Legal / Conversion Rate: 30.02%

High-Converting Landing Page: ClaimCompass
Image courtesy of ClaimCompass. (Click image to see the full page.)

ClaimCompass’s high-converting hint: Ensure visitors have enough information to convert (and then ask them again).

What’s that old saying? “If at first they don’t convert, try, try again”? (It’s not. Please don’t say that to people.)

However, that’s precisely what ClaimCompass did for this landing page targeting travelers who’d been on delayed flights to, from, and within the European Union, where legislation mandates that airlines pay compensation for significant travel disruptions.

Alexander Sumin, the company’s Co-Founder and CMO, described the surprisingly difficult task of getting people to collect their no-strings cash.

We tried to provide some valuable information and back it with authority—not only the social proof and media logos, but briefly explaining how it all works.

That adds more credibility to the offer, which is important when you’re promising free money.

ClaimCompass recognized that they’d be talking to customers with varying degrees of EU regulatory expertise. (Any GDPR-heads out there?) As such, they knew some people would have enough information to convert right away while others would need some educating.

“The entire landing page is designed to make people click on one of the three CTA buttons,” Alex explained. “If the offer is appealing, they don’t need to scroll further. If it isn’t, the sections below provide more clarity on the process, with images, benefits, and social proof. Each scroll is supposed to get the users closer to clicking the CTA.”

10. Extreme Lounging

Industry: Furniture

High-Converting Landing Page: Extreme Lounging
Image courtesy of Extreme Lounging. (Click image to see the full page.)

Extreme Lounging’s high-converting hint: Run giveaway campaigns to drive leads like crazy.

Extreme Lounging might have the simplest landing page in this list from a copy and design perspective—but, boy, it sure is effective.

The whole page amounts to a hero image, headline, and email form, prompting visitors to register for a chance to win a limited edition chair. There are no listed benefits, no competitive differentiators. (Presumably Extreme Lounging has done some of that legwork before people hit this page.) Here, it’s all about building leads. You want this chair? Cool, give us your email. No reason to make things complicated.

Some marketers will object to the basic style, but it’s hard to argue with Extreme Lounging’s results. They’ve been running a new contest (with a new landing page) each month for over half a year, and although they prefer to keep the exact number under wraps, suffice to say that their conversion rate would make you blush.

It just goes to show: no matter how good you look or sweet you talk, nothing motivates people quite like free.

11. onX

Industry: Navigation / Conversion Rate: 61.15%

High-Converting Landing Page: onX
Image courtesy of onX. (Click image to see the full page.)

onX’s high-converting hint: Match visitor search intent in written and visual content.

Something we at Unbounce have really hammered home over the years is the importance of message match. When someone clicks a Google ad for, say, topographic hunting maps, they expect to land on a page with copy that aligns with their original search intent. Even better? A page that immediately demonstrates the searcher is in the right place through the accompanying imagery.

For a great example, look no further than this page from onX, which (at the time of writing) sports a conversion rate over 50% higher than the average. We asked Ryan Watson, User Acquisition Manager at onX, why he thought the landing page has been so successful:

The landing page creative showed the user exactly what they were looking for from a PPC Google Ads search click.

Correlating the search with an exact visual cue is a must with product feature landing pages and search strategy.

Ryan also credits A/B testing for onX’s high-converting landing page. “We tested many different CTAs, and we found one that worked and got a massive click-through rate.” Hey, landing page best practices never hurt, either.

How does your conversion rate stack up?

Download the Unbounce Conversion Benchmark Report to see how your landing page performance compares to competitors in your industry.

var eventMethod=window.addEventListener?”addEventListener”:”attachEvent”,eventer=window[eventMethod],messageEvent=”attachEvent”==eventMethod?”onmessage”:”message”,estop=!1;eventer(messageEvent,function(e)if(“inline”==e.data[2])e.data[1]=e.data[1]+11,document.getElementById(e.data[0]).style.height=e.data[1]+”px”;else,!1);

By entering your email, you consent to receive other resources to help you improve your conversion rates.

12. Investing Shortcuts

Industry: Finance / Conversion Rate: 51.32%

High-Converting Landing Page: Investing Shortcuts
Image courtesy of Investing Shortcuts. (Click image to see the full page.)

Investing Shortcuts’s high-converting hint: Create urgency in your offer whenever possible.

Fear of missing out (FOMO) is one of the most powerful tools in every marketer’s arsenal. People hate it when their peers are having fun, being cool, or making money without them. It’s petty and vindictive, sure, but it’s also innately human. (Man, we’re picking on our species today.)

This landing page for Investing Shortcuts (built by Strikepoint Media) harnesses FOMO to push conversions into overdrive. The copy highlights the meteoric rise of Bitcoin’s value and urges visitors to get in while the gettin’s still good. “This page had the most success when Bitcoin was hot, so it was the right offer and the right time,” explained Jeremy Blossom, Co-Founder and CEO of Strikepoint. Anyone out there still HODLing?

Bitcoin’s popularity aside, a lot of what makes this a high-converting page comes down to good fundamentals. “While it isn’t the prettiest page, the copy connects with readers and builds on their interest in the subject matter while clearly communicating the value of the guide,” Jeremy noted. “The page also uses the ‘featured on’ logos and a high-profile quote for social proof.”

13. MyTutor

Industry: Education / Conversion Rate: 55.29%

High-Converting Landing Page: MyTutor
Image courtesy of MyTutor. (Click image to see the full page.)

MyTutor’s high-converting hint: Present the right offer to the right people at the right time.

So much of a campaign’s success comes down to effective targeting. It’s not just about reaching your target demographic—it’s also about presenting them with highly-targeted offers that make sense in the context of their experience at that particular moment.

Our previous example from Investing Shortcuts demonstrates how an offer can be well-timed for a major cultural event (like the crypto-frenzy of late 2017). This landing page from MyTutor, though, goes one step further. It shows how marketers can connect with their audience at a significant (and even deeply personal) moment in their individual lives, during which the offer is especially meaningful.

Gemma Pearson, Digital Marketing Manager at MyTutor, explains: “This landing page was a fundamental part of our exam results day campaign. It was designed to encourage students who hadn’t achieved the grades they needed to get back on track with a tutor to support their needs.”

Most of us have done poorly on a test, and (I’m comfortable speaking for all of us here) it sucks. The last thing Gemma wanted to do with this page is appear to be scolding or lecturing students that might need a little help.

The relevant, positive messaging—along with timing and a clear CTA—were key factors in this landing page’s success.

It provided messaging that both empathized with their situation and offered a clear solution to get the results they needed.

Now that’s how you make a pitch that resonates.

14. College Board

Industry: Education / Conversion Rate: 77.38%

High-Converting Landing Page: College Board
Image courtesy of College Board. (Click image to see the full page.)

College Board’s high-converting hint: Set an expiry date on your call to action.

Like the past couple of landing pages, this one from College Board—a nonprofit aimed at expanding access to higher education—is all about using time to motivate conversions.

The goal here is getting would-be college applicants (who’ve already taken the PSAT/NMSQT) to register for an upcoming SAT and improve their chances of being accepted at the school of their choice. That oughta be incentive enough, but sometimes (and I’m drawing heavily on my own meandering academic experience) students need a kick in the pants. And if there’s one thing they understand, it’s deadlines.

College Board makes it super clear how long students still have to sign up for the next SAT by including a countdown just below the top CTA and a hard cutoff date alongside the bottom. Coupled with copy that’s one part urgency (“Seats are filling up fast!”) and another part encouragement (“You’re already prepared!”), this landing page successfully urges students to take the next step in their academic careers.

15. FilterEasy

Industry: Home Repair / Conversion Rate: 34.52%

High-Converting Landing Page: FilterEasy
Image courtesy of FilterEasy. (Click image to see the full page.)

FilterEasy’s high-converting hint: It’s not always clear why a landing page is successful—and that’s okay, too.

Every so often, you’ll build a landing page that strikes conversion gold. It’s got a higher form-fill rate than you’ve ever seen. It’s driving revenue like crazy. It’s cutting down challengers like Russell Crowe in that movie about gladiators. (What was it called?)

That’s what happened to Rianna Riddle, Growth Marketing Director at FilterEasy. She built a killer page, then found herself grappling with a question we’ve often asked ourselves: what exactly is making this page successful?

“Honestly, we’re still constantly testing to figure out what’s so great about this landing page,” Ri explained. “We’ve challenged it several times, and none of the challengers have beat this champion page—even the ones we were absolutely convinced would beat it.”

The reality is that building high-converting landing pages isn’t an exact science. Sure, there are best practices that can improve your page’s chances of success, and Ri employs them here: straightforward design, strong benefits statements, great social proof, compelling offer. Ultimately, though, the only way we can be confident that we’ve achieved our best page is by continuing to test.

Handling Unused CSS In SASS To Improve Performance

Handling Unused CSS In SASS To Improve Performance

Handling Unused CSS In SASS To Improve Performance

Luke Harrison

2019-08-09T12:30:59+02:00
2019-08-11T03:35:28+00:00

In modern front-end development, developers should aim to write CSS which is scalable and maintainable. Otherwise, they risk losing control over specifics such as the cascade and selector specificity as the codebase grows and more developers contribute.

One way this can be achieved is through the use of methodologies such as Object-Oriented CSS (OOCSS), which rather than organizing CSS around page context, encourages separating structure (grid systems, spacing, widths, etc.) from decoration (fonts, brand, colors, etc.).

So CSS class names such as:

  • .blog-right-column
  • .latest_topics_list
  • .job-vacancy-ad

Are replaced with more reusable alternatives, which apply the same CSS styles, but aren’t tied to any particular context:

  • .col-md-4
  • .list-group
  • .card

This approach is commonly implemented with the help of a SASS framework such as Bootstrap, Foundation, or increasingly more often, a bespoke framework which can be shaped to better fit the project.

So now we’re using CSS classes cherry-picked from a framework of patterns, UI components and utility classes. The below example illustrates a common grid system built using Bootstrap, which stacks vertically, then once the md breakpoint is reached, switches to a 3 column layout.

<div class="container">
   <div class="row">
      <div class="col-12 col-md-4">Column 1</div>
      <div class="col-12 col-md-4">Column 2</div>
      <div class="col-12 col-md-4">Column 3</div>
   </div>
</div>

Programmatically generated classes such as .col-12 and .col-md-4 are used here to create this pattern. But what about .col-1 through .col-11, .col-lg-4, .col-md-6 or .col-sm-12? These are all examples of classes which will be included in the compiled CSS stylesheet, downloaded and parsed by the browser, despite not being in use.

In this article, we’ll start by exploring the impact unused CSS can have on page load speeds. We’ll then touch upon some existing solution for removing it from stylesheets, following up with my own SASS oriented solution.

Measuring The Impact Of Unused CSS Classes

Whilst I adore Sheffield United, the mighty blades, their website’s CSS is bundled into a single 568kb minified file, which comes to 105kb even when gzipped. That seems like a lot.

This is Sheffield United’s website, my local Football team (that’s Soccer for you lot over in the colonies ??). (Large preview)

Shall we see how much of this CSS is actually used by on their homepage? A quick Google search reveals plenty of online tools up to the job, but I prefer to use the coverage tool in Chrome, which can be run straight from Chrome’s DevTools. Let’s give it a whirl.

The quickest way to access the coverage tool in Developer Tools is to use the keyboard shortcut Control+Shift+P or Command+Shift+P (Mac) to open the command menu. In it, type coverage, and select the ‘Show Coverage’ option. (Large preview)

The results show that only 30kb of CSS from the 568kb stylesheet is used by the homepage, with the remaining 538kb relating to the styles required for the rest of the website. This means a whopping 94.8% of the CSS is unused.

You can see timings like these for any asset in Chrome in Developer Tools via Network -> Click on your asset -> Timing tab. (Large preview)

CSS is part of a webpage’s critical rendering path, which involves all the different steps a browser must complete before it can begin page render. This makes CSS a render-blocking asset.

So with this in mind, when loading Sheffield United’s website using a good 3G connection, it takes a whole 1.15s before the CSS is downloaded and page rendering can begin. This is a problem.

Google has recognized this as well. When running a Lighthouse audit, online or via your browser, any potential load time and filesize savings which could be made by removing unused CSS are highlighted.

In Chrome (and Chromium Edge), you can right Google Lighthouse audits by clicking on the Audit tab in developer tools. (Large preview)

Existing Solutions

The goal is to determine which CSS classes aren’t required and remove them from the stylesheet. Existing solutions are available which attempt to automate this process. They can typically be used via a Node.js build script, or via task runners such as Gulp. These include:

These generally work in a similar way:

  1. On bulld, the website is accessed via a headless browser (Eg: puppeteer) or DOM emulation (Eg: jsdom).
  2. Based on the page’s HTML elements, any unused CSS is identified.
  3. This is removed from the stylesheet, leaving only what is needed.

Whilst these automated tools are perfectly valid and I’ve used many of them across a number of commercial projects successfully, I’ve encountered a few drawbacks along the way which are worth sharing:

  • If class names contain special characters such as ‘@’ or ‘/’, these may not be recognized without writing some custom code. I use BEM-IT by Harry Roberts, which involves structuring class names with responsive suffixes like: u-width-6/12@lg, so I’ve hit this issue before.
  • If the website uses automated deployment, it can slow down the build process, especially if you have lots of pages and lots of CSS.
  • Knowledge about these tools needs to be shared across the team, otherwise there may be confusion and frustration when CSS is mysteriously absent in production stylesheets.
  • If your website has many 3rd party scripts running, sometimes when opened in a headless browser, these don’t play nicely and can cause errors with the filtering process. So typically you have to write custom code to exclude any 3rd party scripts when a headless browser is detected, which depending on your setup, may be tricky.
  • Generally, these kind of tools are complicated and introduce a lot of extra dependencies to the build process. As is the case with all 3rd party dependencies, this means relying on somebody else’s code.

With these points in mind, I posed myself a question:

Using just SASS, is it possible to better handle the SASS we compile so any unused CSS can be excluded, without resorting to just crudely deleting the source classes in the SASS outright?

Spoiler alert: The answer is yes. Here’s what I’ve come up with.

SASS Oriented Solution

The solution needs to provide a quick and easy way to cherry-pick what SASS ought to be compiled, whilst being simple enough that it doesn’t add any more complexity to the development process or prevent developers from taking advantage of things like programmatically generated CSS classes.

To get started, there’s a repo with build scripts and a few sample styles which you can clone from here.

Tip: If you get stuck, you can always cross-reference with the completed version on master branch.

cd into the repo, run npm install and then npm run build to compile any SASS into CSS as required. This should create a 55kb css file in the dist directory.

If you then open /dist/index.html in your web browser, you should see a fairly standard component, which on click expands to reveal some content. You can also view this here, where real network conditions will be applied, so you can run your own tests.

We’ll use this expander UI component as our test subject when developing the SASS-oriented solution for handling unused CSS. (Large preview)

Filtering At The Partials Level

In a typical SCSS setup, you’re likely going to have a single manifest file (eg: main.scss in the repo), or one per page (eg: index.scss, products.scss, contact.scss) where framework partials are imported. Following OOCSS principles, those imports may look something like this:

Example 1
/*
Undecorated design patterns
*/

@import 'objects/box';
@import 'objects/container';
@import 'objects/layout';

/*
UI components
*/

@import 'components/button';
@import 'components/expander';
@import 'components/typography';

/*
Highly specific helper classes
*/

@import 'utilities/alignments';
@import 'utilities/widths';

If any of these partials aren’t in use, then the natural way of filtering this unused CSS would be to just disable the import, which would prevent it from being compiled.

For example, if only using the expander component, the manifest would typically look like the below:

Example 2
/*
Undecorated design patterns
*/
// @import 'objects/box';
// @import 'objects/container';
// @import 'objects/layout';

/*
UI components
*/
// @import 'components/button';
@import 'components/expander';
// @import 'components/typography';

/*
Highly specific helper classes
*/
// @import 'utilities/alignments';
// @import 'utilities/widths';

However, as per OOCSS, we’re separating decoration from structure to allow for maximum reusability, so it’s possible the expander could require CSS from other objects, component or utility classes to render correctly. Unless the developer is aware of these relationships by inspecting the HTML, they may not know to import these partials, so not all of the required classes would be compiled.

In the repo, if you look at the expander’s HTML in dist/index.html, this appears to be the case. It uses styles from the box and layout objects, the typography component, and width and alignment utilities.

dist/index.html
<div class="c-expander">
   <div class="o-box o-box--spacing-small c-expander__trigger c-expander__header" tabindex="0">
      <div class="o-layout o-layout--fit u-flex-middle">
         <div class="o-layout__item u-width-grow">
            <h2 class="c-type-echo">Toggle Expander</h2>
         </div>
         <div class="o-layout__item u-width-shrink">
            <div class="c-expander__header-icon"></div>
         </div>
      </div>
   </div>
   <div class="c-expander__content">
      <div class="o-box o-box--spacing-small">
       Lorum ipsum
         <p class="u-align-center">
            <button class="c-expander__trigger c-button">Close</button>
         </p>
      </div>
   </div>
</div>

Let’s tackle this problem waiting to happen by making these relationships official within the SASS itself, so once a component is imported, any dependencies will also be imported automatically. This way, the developer no longer has the extra overhead of having to audit the HTML to learn what else they need to import.

Programmatic Imports Map

For this dependency system to work, rather than simply commenting in @import statements in the manifest file, SASS logic will need to dictate if partials will be compiled or not.

In src/scss/settings, create a new partial called _imports.scss, @import it in settings/_core.scss, and then create the following SCSS map:

src/scss/settings/_core.scss
@import 'breakpoints';
@import 'spacing';
@import 'imports';
src/scss/settings/_imports.scss
$imports: (
   object: (
    'box',
    'container',
    'layout'
   ),
   component: (
    'button',
    'expander',
    'typography'
   ),
   utility: (
    'alignments',
    'widths'
   )
);

This map will have the same role as the import manifest back in example 1.

Example 4
$imports: (
   object: (
    //'box',
    //'container',
    //'layout'
   ),
   component: (
    //'button',
    'expander',
    //'typography'
   ),
   utility: (
    //'alignments',
    //'widths'
   )
);

It should behave like a standard set of @imports would, in that if certain partials are commented out (like the above), then that code shouldn’t be compiled on build.

But as we’re wanting to import dependencies automatically, we should also be able to ignore this map under the right circumstances.

Render Mixin

Let’s start to add some SASS logic. Create _render.scss in src/scss/tools, and then add its @import to tools/_core.scss.

In the file, create an empty mixin called render().

src/scss/tools/_render.scss
@mixin render() 

In the mixin, we need to write SASS which does the following:

  • render()
    “Hey there $imports, fine weather isn’t it? Say, do you have the container object in your map?”
  • $imports
    false
  • render()
    “That’s a shame, looks like its won’t be compiled then. How about the button component?”
  • $imports
    true
  • render()
    “Nice! That’s the button being compiled then. Say hi to the wife for me.”

In SASS, this translates to the following:

src/scss/tools/_render.scss
@mixin render($name, $layer) 
   @if(index(map-get($imports, $layer), $name)) 
      @content;
   

Basically, check if the partial is included in the $imports variable, and if so, render it using SASS’s @content directive, which allows us to pass a content block into the mixin.

We would use it like so:

Example 5
@include render('button', 'component') 
   .c-button 
      // styles et al
   

   // any other class declarations

Before using this mixin, there’s a small improvement which we can make to it. The layer name (object, component, utility, etc.) is something we can safely predict, so we have an opportunity to streamline things a little.

Before the render mixin declaration, create a variable called $layer, and remove the identically named variable from the mixins parameters. Like so:

src/scss/tools/_render.scss
$layer: null !default;
@mixin render($name) 
   @if(index(map-get($imports, $layer), $name)) 
      @content;
   

Now, in the _core.scss partials where objects, components and utility @imports are located, redeclare these variables to the following values; representing the type of CSS classes being imported.

src/scss/objects/_core.scss
$layer: 'object';

@import 'box';
@import 'container';
@import 'layout';
src/scss/components/_core.scss
$layer: 'component';

@import 'button';
@import 'expander';
@import 'typography';
src/scss/utilities/_core.scss
$layer: 'utility';

@import 'alignments';
@import 'widths';

This way, when we use the render() mixin, all we have to do is declare the partial name.

Wrap the render() mixin around each object, component and utility class declaration, as per the below. This will give you one render mixin usage per partial.

For example:

src/scss/objects/_layout.scss
@include render('button') 
   .c-button 
      // styles et al
   

   // any other class declarations
src/scss/components/_button.scss
@include render('button') 
   .c-button 
      // styles et al
   

   // any other class declarations

Note: For utilities/_widths.scss, wrapping the render() function around the entire partial will error on compile, as in SASS you can’t nest mixin declarations within mixin calls. Instead, just wrap the render() mixin around the create-widths() calls, like below:

@include render('widths') 

// GENERATE STANDARD WIDTHS
//---------------------------------------------------------------------

// Example: .u-width-1/3
@include create-widths($utility-widths-sets);

// GENERATE RESPONSIVE WIDTHS
//---------------------------------------------------------------------

// Create responsive variants using settings.breakpoints
// Changes width when breakpoint is hit
// Example: .u-width-1/3@md

@each $bp-name, $bp-value in $mq-breakpoints 
   @include mq(#$bp-name) 
      @include create-widths($utility-widths-sets, @, #$bp-name);
   


// End render

With this in place, on build, only the partials referenced in $imports will be compiled.

Mix and match what components are commented out in $imports and run npm run build in the terminal to give it a try.

Dependencies Map

Now we’re programmatically importing partials, we can start to implement the dependency logic.

In src/scss/settings, create a new partial called _dependencies.scss, @import it in settings/_core.scss, but make sure it’s after _imports.scss. Then in it, create the following SCSS map:

src/scss/settings/_dependencies.scss
$dependencies: (
   expander: (
      object: (
         'box',
         'layout'
    ),
    component: (
         'button',
         'typography'
    ),
    utility: (
         'alignments',
         'widths'
    )
   )
);

Here, we declare dependencies for the expander component as it requires styles from other partials to render correctly, as seen in dist/index.html.

Using this list, we can write logic which would mean these dependencies would always be compiled along with their dependent components, no matter the state of the $imports variable.

Below $dependencies, create a mixin called dependency-setup(). In here, we’ll do the following actions:

1. Loop through the dependencies map.
@mixin dependency-setup() 
   @each $componentKey, $componentValue in $dependencies 
    
   
2. If the component can be found in $imports, loop through its list of dependencies.
@mixin dependency-setup() 
   $components: map-get($imports, component);
   @each $componentKey, $componentValue in $dependencies 
      @if(index($components, $componentKey)) 
         @each $layerKey, $layerValue in $componentValue 

         
      
   
3. If the dependency isn’t in $imports, add it.
@mixin dependency-setup() 
   $components: map-get($imports, component);
   @each $componentKey, $componentValue in $dependencies 
       @if(index($components, $componentKey)) 
           @each $layerKey, $layerValue in $componentValue 
               @each $partKey, $partValue in $layerValue 
                   @if not index(map-get($imports, $layerKey), $partKey) 
                       $imports: map-merge($imports, (
                           $layerKey: append(map-get($imports,  $layerKey), '#$partKey')
                       )) !global;
                   
               
           
       
   

Including the !global flag tells SASS to look for the $imports variable in the global scope, rather than the mixin’s local scope.

4. Then it’s just a matter of calling the mixin.
@mixin dependency-setup() 
   ...

@include dependency-setup();

So what we have now is an enhanced partial import system, where if a component is imported, a developer doesn’t then have to manually import each of its various dependency partials as well.

Configure the $imports variable so only the expander component is imported and then run npm run build. You should see in the compiled CSS the expander classes along with all of its dependencies.

However, this doesn’t really bring anything new to the table in terms of filtering out unused CSS, as the same amount of SASS is still being imported, programmatic or not. Let’s improve on this.

Improved Dependency Importing

A component may require only a single class from a dependency, so to then go on and import all of that dependency’s classes just leads to the same unnecessary bloat we’re trying to avoid.

We can refine the system to allow for more granular filtering on a class by class basis, to make sure components are compiled with only the dependency classes they require.

With most design patterns, decorated or not, there exists a minimum amount of classes which need to be present in the stylesheet for the pattern to display correctly.

For class names using an established naming convention such as BEM, typically the “Block” and “Element” named classes are required as a minimum, with “Modifiers” typically being optional.

Note: Utility classes wouldn’t typically follow the BEM route, as they’re isolated in nature due to their narrow focus.

For example, take a look at this media object, which is probably the most well-known example of object-oriented CSS:

<div class="o-media o-media--spacing-small">
   <div class="o-media__image">
     <img src="url" alt="Image">
   </div>
   <div class="o-media__text">
      Oh!
   </div>
</div>

If a component has this set as a dependency, it makes sense to always compile .o-media, .o-media__image and .o-media__text, as that’s the minimum amount of CSS required to make the pattern work. However with .o-media--spacing-small being an optional modifier, it ought to only be compiled if we explicitly say so, as its usage may not be consistent across all media object instances.

We’ll modify the structure of the $dependencies map to allow us to import these optional classes, whilst including a way to import only the block and element in case no modifiers are required.

To get started, check the expander HTML in dist/index.html and make a note of any dependency classes in use. Record these in the $dependencies map, as per below:

src/scss/settings/_dependencies.scss
$dependencies: (
   expander: (
       object: (
           box: (
               'o-box--spacing-small'
           ),
           layout: (
               'o-layout--fit'
           )
       ),
       component: (
           button: true,
           typography: (
               'c-type-echo',
           )
       ),
       utility: (
           alignments: (
               'u-flex-middle',
               'u-align-center'
           ),
           widths: (
               'u-width-grow',
               'u-width-shrink'
           )
       )
   )
);

Where a value is set to true, we’ll translate this into “Only compile block and element level classes, no modifiers!”.

The next step involves creating a whitelist variable to store these classes, and any other (non-dependency) classes we wish to manually import. In /src/scss/settings/imports.scss, after $imports, create a new SASS list called $global-filter.

src/scss/settings/_imports.scss
$global-filter: ();

The basic premise behind $global-filter is that any classes stored here will be compiled on build as long as the partial they belong to is imported via $imports.

These class names could be added programmatically if they’re a component dependency, or could be added manually when the variable is declared, like in the example below:

Global filter example
$global-filter: (
   'o-box--spacing-regular@md',
   'u-align-center',
   'u-width-6/12@lg'
);

Next, we need to add a bit more logic to the @dependency-setup mixin, so any classes referenced in $dependencies are automatically added to our $global-filter whitelist.

Below this block:

src/scss/settings/_dependencies.scss
@if not index(map-get($imports, $layerKey), $partKey) 

…add the following snippet.

src/scss/settings/_dependencies.scss
@each $class in $partValue 
   $global-filter: append($global-filter, '#$class', 'comma') !global;

This loops through any dependency classes and adds them to the $global-filter whitelist.

At this point, if you add a @debug statement below the dependency-setup() mixin to print out the contents of $global-filter in the terminal:

@debug $global-filter;

…you should see something like this on build:

DEBUG: "o-box--spacing-small", "o-layout--fit", "c-box--rounded", "true", "true", "u-flex-middle", "u-align-center", "u-width-grow", "u-width-shrink"

Now we’ve got a class whitelist, we need to enforce this across all of the different object, component and utility partials.

Create a new partial called _filter.scss in src/scss/tools and add an @import to tools layer’s _core.scss file.

In this new partial, we’ll create a mixin called filter(). We’ll use this to apply logic which means classes will only be compiled if included in the $global-filter variable.

Starting off simple, create a mixin which accepts a single parameter — the $class which the filter controls. Next, if the $class is included in the $global-filter whitelist, allow it to be compiled.

src/scss/tools/_filter.scss
@mixin filter($class) 
   @if(index($global-filter, $class)) 
      @content;
   

In a partial, we would wrap the mixin around an optional class, like so:

@include filter('o-myobject--modifier') 
   .o-myobject--modifier 
      color: yellow;
   

This means the .o-myobject--modifier class would only be compiled if its included in $global-filter, which can either be set directly, or indirectly through what’s set in $dependencies.

Go through the repo and apply the filter() mixin to all optional modifier classes across object and component layers. When handling the typography component or the utilities layer, as each class is independent from the next, it’d make sense to make them all optional, so we can then just enable classes as we need them.

Here’s a few examples:

src/scss/objects/_layout.scss
@include filter('o-layout__item--fit-height') 
    .o-layout__item--fit-height 
        align-self: stretch;
    
src/scss/utilities/_alignments.scss
// Changes alignment when breakpoint is hit
// Example: .u-align-left@md
@each $bp-name, $bp-value in $mq-breakpoints 
    @include mq(#$bp-name) 
        @include filter('u-align-left@#$bp-name') 
            .u-align-left@#$bp-name 
                text-align: left !important;
            
        

        @include filter('u-align-center@#$bp-name') 
            .u-align-center@#$bp-name 
                text-align: center !important;
            
        

        @include filter('u-align-right@#$bp-name') 
            .u-align-right@#$bp-name 
                text-align: right !important;
            
        
    

Note: When adding the responsive suffix classnames to the filter() mixin, you don’t have to escape the ‘@’ symbol with a ”.

During this process, whilst applying the filter() mixin to partials, you may (or may not) have noticed a few things.

Grouped Classes

Some classes in the codebase are grouped together and share the same styles, for example:

src/scss/objects/_box.scss
.o-box--spacing-disable-left,
.o-box--spacing-horizontal 
    padding-left: 0;

As the filter only accepts a single class, it doesn’t account for the possibility that one style declaration block may be for more than one class.

To account for this, we’ll expand the filter() mixin so in addition to a single class, it’s able to accept a SASS arglist containing many classes. Like so:

src/scss/objects/_box.scss
@include filter('o-box--spacing-disable-left', 'o-box--spacing-horizontal') 
    .o-box--spacing-disable-left,
    .o-box--spacing-horizontal 
        padding-left: 0;
    

So we need to tell the filter() mixin that if either of these classes are in the $global-filter, you are allowed to compile the classes.

This will involve additional logic to type check the mixin’s $class argument, responding with a loop if an arglist is passed to check if each item is in the $global-filter variable.

src/scss/tools/_filter.scss
@mixin filter($class...) 
    @if(type-of($class) == 'arglist') 
        @each $item in $class 
            @if(index($global-filter, $item)) 
                @content;
            
        
    
    @else if(index($global-filter, $class)) 
        @content;
    

Then it’s just a matter of going back to the following partials to correctly apply the filter() mixin:

  • objects/_box.scss
  • objects/_layout.scss
  • utilities/_alignments.scss

At this point, go back to $imports and enable just the expander component. In the compiled stylesheet, besides the styles from the generic and elements layers, you should only see the following:

  • The block and element classes belonging to the expander component, but not its modifier.
  • The block and element classes belonging to the expander’s dependencies.
  • Any modifier classes belonging to the expander’s dependencies which are explicitly declared in the $dependencies variable.

Theoretically, if you decided you wanted to include more classes in the compiled stylesheet, such as the expander components modifier, it’s just a matter of adding it to the $global-filter variable at the point of declaration, or appending it at some other point in the codebase (As long as it’s before the point where the modifier itself is declared).

Enabling Everything

So we now have a pretty complete system, which lets you import objects, components and utilities down to the individual classes within these partials.

During development, for whatever reason, you may just want to enable everything in one go. To allow for this, we’ll create a new variable called $enable-all-classes, and then add in some additional logic so if this is set to true, everything is compiled no matter the state of the $imports and $global-filter variables.

First, declare the variable in our main manifest file:

src/scss/main.scss
$enable-all-classes: false;

@import 'settings/core';
@import 'tools/core';
@import 'generic/core';
@import 'elements/core';
@import 'objects/core';
@import 'components/core';
@import 'utilities/core';

Then we just need to make a few minor edits to our filter() and render() mixins to add some override logic for when the $enable-all-classes variable is set to true.

First up, the filter() mixin. Before any existing checks, we’ll add an @if statement to see if $enable-all-classes is set to true, and if so, render the @content, no questions asked.

src/scss/tools/_filter.scss
@mixin filter($class...) 
    @if($enable-all-classes) 
        @content;
    
    @else if(type-of($class) == 'arglist') 
        @each $item in $class 
            @if(index($global-filter, $item)) 
                @content;
            
        
    
    @else if(index($global-filter, $class)) 
        @content;
    

Next in the render() mixin, we just need to do a check to see if the $enable-all-classes variable is truthy, and if so, skip any further checks.

src/scss/tools/_render.scss
$layer: null !default;
@mixin render($name) 
    @if($enable-all-classes or index(map-get($imports, $layer), $name)) 
        @content;
    

So now, if you were to set the $enable-all-classes variable to true and rebuild, every optional class would be compiled, saving you quite a bit of time in the process.

Comparisons

To see what type of gains this technique is giving us, let’s run some comparisons and see what the filesize differences are.

To make sure the comparison is a fair one, we ought to add the box and container objects in $imports, and then add the box’s o-box--spacing-regular modifier to the $global-filter, like so:

src/scss/settings/_imports.scss
$imports: (
     object: (
        'box',
        'container'
        // 'layout'
     ),
     component: (
        // 'button',
        'expander'
        // 'typography'
     ),
     utility: (
        // 'alignments',
        // 'widths'
     )
);

$global-filter: (
    'o-box--spacing-regular'
);

This makes sure styles for the expander’s parent elements are being compiled like they would be if there were no filtering taking place.

Original vs Filtered Stylesheets

Let’s compare the original stylesheet with all classes compiled, against the filtered stylesheet where only CSS required by the expander component has been compiled.

Standard
StylesheetSize (kb)Size (gzip)
Original54.6kb6.98kb
Filtered15.34kb (72% smaller)4.91kb (29% smaller)

You may think that the gzip percentage savings mean this isn’t worth the effort, as there’s not much difference between the original and filtered stylesheets.

It’s worth highlighting that gzip compression works better with larger and more repetitive files. Because the filtered stylesheet is the only proof-of-concept, and only contains CSS for the expander component, there isn’t as much to compress as there would be in a real-life project.

If we were to scale up each stylesheet by a factor of 10 to sizes more typical of a website’s CSS bundle size, the difference in gzip file sizes are much more impressive.

10x Size
StylesheetSize (kb)Size (gzip)
Original (10x)892.07kb75.70kb
Filtered (10x)209.45kb (77% smaller)19.47kb (74% smaller)

Filtered Stylesheet vs UNCSS

Here’s a comparison between the filtered stylesheet and a stylesheet which has been run through the UNCSS tool.

Filtered vs UNCSS
StylesheetSize (kb)Size (gzip)
Filtered15.34kb4.91kb
UNCSS12.89kb (16% smaller)4.25kb (13% smaller)

The UNCSS tool wins here marginally, as it’s filtering out CSS in the generic and elements directories.

It’s possible that on a real website, with a larger variety of HTML elements in use, the difference between the 2 methods would be negligible.

Wrapping Up

So we’ve seen how — using just SASS — you are able to gain more control over what CSS classes are being compiled on build. This reduces the amount of unused CSS in the final stylesheet and speeds up the critical rendering path.

At the start of the article, I listed some drawbacks of existing solutions such as UNCSS. It’s only fair to critique this SASS-oriented solution in the same way, so all the facts are on the table before you decide which approach is better for you:

Pros

  • No additional dependencies required, so you don’t have to rely on somebody else’s code.
  • Less build time required than Node.js based alternatives, as you don’t have to run headless browsers to audit your code. This is especially useful with continuous integration as you may be less likely to see a queue of builds.
  • Results in similar file size when compared to automated tools.
  • Out of the box, you have complete control over what code is being filtered, regardless of how those CSS classes are used in your code. With Node.js based alternatives, you often have to maintain a separate whitelist so CSS classes belonging to dynamically injected HTML aren’t filtered out.

Cons

  • The SASS-oriented solution is definitely more hands-on, in the sense that you have to keep on top of the $imports and $global-filter variables. Beyond the initial setup, the Node.js alternatives we’ve looked at are largely automated.
  • If you add CSS classes to $global-filter and then later remove them from your HTML, you need to remember to update the variable, otherwise you’ll be compiling CSS you don’t need. With large projects being worked on by multiple devs at any one time, this may not be easy to manage unless you properly plan for it.
  • I wouldn’t recommend bolting this system onto any existing CSS codebase, as you’d have to spend quite a bit of time piecing together dependencies and applying the render() mixin to a LOT of classes. It’s a system much easier to implement with new builds, where you don’t have existing code to contend with.

Hopefully you’ve found this as interesting to read as I’ve found it interesting to put together. If you have any suggestions, ideas to improve this approach, or want to point out some fatal flaw that I’ve missed entirely, be sure to post in the comments below.

Smashing Editorial
(dm, yk, il)