Menu Search
Jump to the content X X
Smashing Conf San Francisco

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf San Francisco, dedicated to smart front-end techniques and design patterns.

Building Social: A Case Study On Progressive Enhancement

We talk a lot about progressive enhancement and how it improves backwards compatibility. But how straightforward is it to apply progressive enhancement concepts to a real-life project? When designing a rich interactive experience, it can be difficult to determine what can be implemented purely using HTML and CSS and what absolutely requires JavaScript.

Through this case study on redesigning the Building Social1 website, we’ll share some simple yet often overlooked front-end techniques that defer the use of JavaScript as much as possible, while providing some neat JavaScript enhancements, too.

Introducing Building Social Link

Building Social is an invitation-only app that connects people who share an office building — who might otherwise never meet — into a building-specific social media platform. People are able to access building events, conversations, likes, replies, trivia contests, a marketplace, and one-click reporting to and from the building’s management, as well as receive emergency and general information notifications.

The initial one-page design concept for the Building Social website was art directed by Patrick Riley and Paul Stanton from their New York office. We were handed a static mock-up of the design concept, along with a video mock-up of how various elements should move. Our job was to experiment with the design further and, more importantly, to bring it to life in the browser.

The initial static mock-up of the website2
The initial static mock-up of the website (View large version3)

Considering that the project was relatively small in size, there was no need to establish a specific design or development strategy — unlike, for instance, with the SGS project4. This meant we were able to focus predominantly on the implementation and a number of specific progressive enhancement challenges, including:

  • automated versus synced string swapping;
  • static versus dynamically blended section backgrounds;
  • manual versus scroll position-based animation toggling;
  • pure-CSS modal-box forms enhanced with JavaScript.

Everything Except HTML Is an Optional Extra Link

Our first task was to set a baseline experience by applying properly structured and semantic HTML5 to all of the design elements. Considering that Patrick and Paul wanted to make use of video content, we also had to ensure there was a fallback for video content6.

Challenge 1: Automated Vs. Synced String Swapping Link

At first glance, the header appears to be just pure decoration. However, it actually contains usable content, promoting several key features of the Building Social service. Patrick and Paul wanted to include a video feature in the header, too, with each video scene associated with a specific service feature.

Progressive enhancement summary:

  • Mark up key service features in an HTML unordered list to provide a baseline experience should the video, JavaScript and/or CSS fail to load.
  • Animate service features via CSS keyframe animations.
  • When the video loads, swap out each service feature after each subsequent scene change, while keeping it in sync with the video playhead.

Having established the baseline HTML experience, we tried simply to animate the list of service features independent of the video using CSS keyframe animations, as each scene lasts for 3 seconds (21 seconds in total). Below is our original CSS snippet:

.list-options li { animation: animate_options 21s linear infinite; }
.list-options li:nth-child(1) { animation-delay: 0s; }
.list-options li:nth-child(2) { animation-delay: 3s; }

/* … and so on. We actually used a Sass 
snippet that specifies the animation delay 
value for each item:

@for $i from 1 through 7 {
  .list-options li:nth-child(#{$i}) { 
    animation-delay: #{($i*3)-3}s;


@keyframes animate_options {
    /* 0s   */  0%      { opacity: 0; bottom: -10vw; }
    /* 0.3s */  1.4%    { opacity: 1; bottom: 0; }
    /* 1.5s */  7.1%    { opacity: 1; bottom: 0; }
    /* 2.7s */  12.6%   { opacity: 1; bottom: 0; }
    /* 3s   */  14%     { opacity: 0; bottom: -10vw; }
    /* 21s  */  100%    { opacity: 0; bottom: -10vw; }

Perhaps not the most elegant CSS snippet you’ll ever see, but it certainly did the job. However, we were fully aware that there might be times when the video fails to load altogether or when JavaScript fails to initiate properly. In such instances, a static background image is displayed with each service feature, swapped out according to the keyframe timings seen in the CSS snippet above.

Each key service feature is swapped out using only CSS.

When the video fully loads, we then have to ensure that each service feature remains in sync after every scene change:

Each key service feature is synced after every scene change in the video.

While we could have simply relied on CSS keyframe animations, the risk was that the animated list would quickly get out of sync should the video finish loading after the other assets. The solution: synced string swapping and a small snippet of JavaScript to keep track of the playhead position.

When the JavaScript loads, it begins to track the video’s playhead position, with the service feature string being swapped out depending on the retrieved value. This is done by listening to the timeupdate event in JavaScript (with jQuery) and toggling the class name for the respective element whenever the video’s currentTime matches the condition:

$('video').on('timeupdate', function() {
  var ct = $('video')[0].currentTime;
    var j = Math.floor(ct + 0.9);
    var k = Math.floor(ct / 3);
    if (j > 0 && j % 3 == 0) {
        $('.list-options li').removeClass('current');
    } else {
        $('.list-options li').eq(k).addClass('current');

You may have also noticed that we used a factor of 0.9 to toggle the class before the actual scene change? This allows enough time for the animation to execute properly, while the animation itself is still controlled via CSS:

.list-options li { bottom: -10vw; opacity: 0; transition: all .3s ease; }
.list-options li.current { bottom: 0; opacity: 1; }

Challenge 2: Static Vs. Dynamically Blended Section Backgrounds Link

By default, each page section has a designated background color, along with one or more background images. Nothing too extravagant there, except that the New York team really wanted the section backgrounds to smoothly blend into each other as the user scrolls down the page.

Static section backgrounds are the default, and blended backgrounds are added as a progressive enhancement.7
Static section backgrounds are the default, and blended backgrounds are added as a progressive enhancement. (View large version8)

Progressive enhancement summary:

  • Set static background colors by default for each page section.
  • Apply enhanced background blending via JavaScript.

Blended backgrounds aren’t something you see or work on every day. Nevertheless, some interesting examples are already out there; for instance, the website for the design agency ustwo9. To create this effect consistently when the user scrolls, we had to use JavaScript, but only as a progressive enhancement. After researching and testing a number of promising JavaScript-based libraries, we settled on Scroll Magic10 for two reasons:

  • It is well documented, with plenty of examples that could be applied to what we needed.
  • It is lightweight, weighing in at 8 KB.

Our initial idea was to detect the position of each successive page section in order to apply the relevant CSS background gradient. However, it soon became apparent that no gradient was being applied whenever we stopped scrolling! Because only a single solid background color was applied to the body element, the gradient was indeed an optical illusion created by the scrolling:

Blended section backgrounds create the illusion of an infinite gradient.

Upon realizing that we had been the victims of an optical illusion up to that stage, we discarded the gradient idea. Instead, we decided simply to create a motion tween between the solid background colors of the adjoining page sections whenever the user scrolls down the page:

var bodyBackground = function() {

    var overviewHeight = parseInt($('#overview').height());
    new ScrollMagic.Scene({
    triggerElement: '#overview',
        offset: overviewHeight,
        duration: overviewHeight / 2
    .setTween('body', { backgroundColor: ‘white' })

    var theAppHeight = parseInt($('#the-app').height());
    new ScrollMagic.Scene({
        triggerElement: '#the-app',
        triggerHook: 1,
        offset: theAppHeight,
        duration: theAppHeight / 2
    .setTween('body', { backgroundColor: ‘blue' })

    var audiencesHeight = parseInt($('#providers').height());
    new ScrollMagic.Scene({
        triggerElement: '#audiences',
        offset: audiencesHeight,
        duration: audiencesHeight / 2
    .setTween('body', { backgroundColor: ‘red' })


Having established that this approach works, we then adopted a similar configuration, using Scroll Magic again, to trigger scene animations that are tied to the scroll position. But before we attempted to do that, we had to make sure that these animations could be triggered without having to rely on JavaScript.

Challenge 3: Manual Vs. Scroll-Position-Based Animation Toggling Link

We really wanted to incorporate the idea of movement whenever the user scrolls down the page, but at the same time ensure that it isn’t overwhelming (as is often the case with a lot of websites that contain the parallax effect).

Progressive enhancement summary:

  • Use a CSS :hover pseudo-class to toggle animations by default.
  • Toggle animations based on the scroll position using JavaScript.

For simple animations that are not central to the content, the animation options available in CSS are more than sufficient. For instance, the animation length and timing functions, coupled with some of the animatable CSS properties such as positioning, margins and opacity, go a long way. Furthermore, if any elements in a single scene don’t require any specific independent interaction, then only a single event is needed to trigger all of the animation elements.

Simple animation triggered with the :hover pseudo-class11
Simple animation triggered with the :hover pseudo-class (View large version12)

In our case, because the sections were 100% wide, we were able to trigger the animation in each section by using the :hover pseudo-class. Doing so gave us a quick and unobtrusive way to trigger the animations without having to use JavaScript. However, this method doesn’t work on touch-enabled devices, and considering that animations are, for the most part, visual enhancements, we decided to discard them for small viewports.

More complex animation triggered when scrolling13
More complex animation triggered when scrolling (View large version14)

Upon detecting that the JavaScript has fully loaded, we trigger the animations based on the scroll position. By also including background elements in the animations and by separately adjusting the timings for each, we are able to control each individual scene more precisely, thus enhancing the overall experience.

Challenge 4: Pure-CSS Modal-Box Form Enhanced With JavaScript Link

Considering that the initial concept was a one-page design, with two distinct calls to action (i.e. for landlords and “providers”), we really wanted to ensure that the user isn’t taken away from the page. Furthermore, due to the floating text on the right, progressively disclosing any subsequent contact forms below each call to action would not have been ideal. Therefore, modal boxes were the best solution.

Progressive enhancement summary:

  • Toggle modal boxes by default using only semantic HTML and CSS.
  • Enhance the experience by introducing inline validation and form submission that does not require page reloading via JavaScript.

Toggling elements on and off is very easy when you write semantic HTML, meaning that elements can be progressively enhanced without polluting the actual code. For instance, by ensuring that each toggled element has a semantic id attribute and anchor link (one that points to the same id), we were able not only to provide hooks to support interesting interactions, but also to dramatically improve the accessibility and usability of the unstyled page.

A pure-CSS modal-box web form15
A pure-CSS modal-box web form (View large version16)

But how exactly can anchor links toggle modal boxes? CSS :target pseudo-class to the rescue! Whenever an element’s id matches the hash string in the browser’s address bar, the :target pseudo-class gets triggered, meaning that the element can also be styled differently. Furthermore, the CSS is quite simple:

/* E.g. URL */
#modal-contact { display: none; }

/* E.g. URL */
#modal-contact:target { display: block; }

If you’re aware of the :target pseudo-class technique (after all, it has been around17 for quite some time now), then you will have probably used it already to toggle elements without having to rely on JavaScript.

When you also include a second anchor link that refers back to the initial anchor element (or its parent), you’re then able to use it as a “close” link. This is because the :target pseudo-class, which matches the modal box’s id, is no longer active when the “close” link is clicked. Here’s a quick example:

<div id="section-01">
  <a href="#modal-contact">Contact us</a>
<div id="modal-contact">
  <!-- contact form -->
  <a href="#section-01">Close</a>

In addition, when you anchor to the element from which the modal was initially toggled, it will set the page precisely back to its position before the user activated the modal.

We can push the use of the :target pseudo-class even further. Let’s say the modal box contains a web form, and we want to display the result of the form submission in that same modal box (for instance, as a confirmation message).

Display the confirmation message in the modal box to create the illusion of speed and to keep the user focused on the task.18
Display the confirmation message in the modal box to create the illusion of speed and to keep the user focused on the task. (View large version19)

To do so, we simply need to add a hash id (an id identical to the modal box’s id) to the resulting URL — the same one we send to the user after they have submitted the form. Here is a simple example using a header redirect in PHP:

<? header('Location:'); ?>

On a really fast Internet connection, it will appear as if the browser has never reloaded the page. On a slow connection or a less-capable device, instead of reloading the page and positioning it towards its head, the browser will automatically skip to the confirmation message, thus dramatically improving the user experience.

With this basic functionality in place, we can enhance it further with JavaScript by introducing inline validation and form submissions that do not require full page reloads20 (using a good ol’ XML HTTP request (XHR), otherwise known as AJAX). Doing so not only will shave off a bit of time, but will make the experience more convenient if there happen to be any input errors.

Lastly, why not provide support for closing the modal box with the Escape key? Following the same principle of toggling classes and anchoring to different elements, the code required to do so is relatively simple:

$(document).on('keyup', function(e) {
    // If there is a visible modal, i.e. .md-show…
    if (e.keyCode == 27 && $('.md-show').length) {

        // Store the target from the modal's close link…
        var newTarget = $('.md-show .md-close').attr('href');

        // Hide the modal   

        // Position the page to the stored target
        document.location.hash = newTarget;

Taking everything we have discussed into account, below is a list of progressive enhancement options for the modal-box contact form:

  1. Toggle and close the model box purely with CSS — JavaScript not required.
  2. Enable basic HTML5 validation using the required attribute and proper input types (for instance, type="email", which will validate the entry against the correct email address pattern) — JavaScript not required.
  3. Fully reload a form submission, automatically opening the form in a modal box, and returning a confirmation message or a server-side-validated error message — JavaScript not required.
  4. Validate input fields inline — requires JavaScript and duplicate validation logic on both the client side and server side, which could prove difficult to maintain.
  5. Submit forms via XHR and return a confirmation message or a server-side-validated error message without reloading the page or closing the modal box.
  6. Additional enhancements are possible — for instance, closing the modal box with the Escape key.

If the form is relatively simple and sent to the server using XHR, then step 4 can be skipped; otherwise, depending on the project, this will create unnecessary maintenance overhead. However, as you’re probably aware, forms should always be validated server-side21, even if you implement client-side validation.

Bonus Tip: The Illusion Of Randomness Link

What do you think of first when you need to randomize something on the client side? Does it involve JavaScript’s Math.random() by any chance?

For this project, we wanted to randomize the clouds in the “Providers” section (the red section), to mimic real cloud movement in nature. Clouds generally sit at different heights, but move more or less in the same direction. Also, from the viewer’s perspective, clouds often appear to travel at different speeds relative to each other.

Clouds move in the same direction but at different speeds.22
Clouds move in the same direction but at different speeds. (View large version23)

This resulted in an animation that uses the same traveling distances but is set at different durations:

.cloud-01 { animation: clouds 7s linear infinite; }
.cloud-02 { animation: clouds 5s linear infinite; }
.cloud-03 { animation: clouds 10s linear infinite; }

@keyframes clouds {
    0%  { opacity: 0; margin-left: 0; }
    50%     { opacity: 1; margin-left: -100px; }
    /* By hiding the cloud at 80%, we provide a break during which the cloud is hidden. */
    80%     { opacity: 0; margin-left: -200px; }
    100%    { opacity: 0; margin-left: -200px; }

The technique above proves that sometimes there is no need to use JavaScript. If we step back and revisit the problem and goal, we can achieve the same result simply by using readily available and widely supported technologies but in a much smarter way.

Conclusion: It Can Be Done! Link

Some of the aforementioned techniques can also be readily applied to other UI elements, such as tabs, tables and dropdowns. By using native HTML functionality, coupled with pseudo-selectors and CSS animation, you can provide relatively simple yet engaging interactive experiences, requiring little effort on your part.

While there’s certainly nothing wrong with using JavaScript to enhance an experience, considering that Javascript-based animation can be just as fast24 as CSS-only animation, do think about semantics25, accessibility and performance before diving head first into any solution. Semantic HTML in particular, which has been widely evangelized26 for many years, reinforces the meaning of content through markup rather than through its presentation. Therefore, investigate multiple options — in most cases, you’ll be able to identify the least complex solution.

Finally, by being creative and using the basic tools at your disposal, you can improve performance and accessibility, as well as simplify code maintenance. Keep in mind that no two projects are ever quite the same, and each is likely to have specific use cases in terms of performance and accessibility. As such, metrics and data may vary. For instance, the UK’s Government Digital Service determined that 1.1%27 (or 1 in 93) of its users miss out on JavaScript enhancements. In attempting to show that progressive enhancement is faster28, Jake Archibald ran a series of convincing tests, although, as he points out, “the uncertainty of real life” can affect the fairness of any test. But the point, as always, is that by getting content on the screen as soon as possible, you will improve the user experience, and in doing so, you will earn a few extra karma points along the way. Everybody wins!

(vf, il, al)

Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28

↑ Back to top Tweet itShare on Facebook

Marko Dugonjić is a designer from Velika Gorica, Croatia. As the user experience director at Creative Nights, he improves customers’ digital experience for local and international clients, speaks at international web design conferences and runs in-house and public UX workshops. He founded Typetester, a popular web typography design tool.

  1. 1

    Nice article and neat CSS techniques used over JS.
    Not sure about the way the H1 is handled though. Is this search friendly?

    • 2

      Hi Michael, thanks for the feedback.

      Regarding the nested list inside the h1, the accessibility validators we tested with haven’t complained and they as well render the correct document outline.

  2. 3

    Awesome article, thanks for that!

    What I’m still asking myself is:
    When you have a progressively enhanced solution, like, for instance your modal. Do you disable the CSS-only solution once you detect that javascript is available and loaded? Because I can imagine that both ways might interfere with each other if both are applied at the same time?
    Or do you try to design around this problem from the start?

    • 4

      Hi AvantiC, thanks! Much appreciated.

      The CSS-only solution should be always JavaScript agnostic. Once the JS loads and takes over, it should take care of JS-specific classes and rebuild the DOM if required.

      For example, if using :target with JS to toggle a modal is not an option, you’d prevent the default link behavior in JavaScript, so that it doesn’t follow the href attribute (in this case a modal ID).

  3. 6


    September 27, 2016 11:38 pm

    Nice work. I wouldn’t go the CSS lightbox route, tho, since it is pretty limited, eg, You can scroll the lightbox ouf of view on mobile etc, lightbox flashes when page reloads etc.

    • 7

      Thanks for the feedback! If it’s structurally placed exactly where it has to appear when toggled, the lightbox / modal doesn’t have to be positioned with position: absolute or position: fixed on mobile. In short, there are ways to make a modal accessible while scrolling.

  4. 8

    Tarik Zakaria Benmerar

    September 28, 2016 7:40 am

    Thanks for this great article. Just one précision. For the modal case, clicking on it’s background space should close it which is not the case now. I dont know if there is a viable css only solution.

    • 9

      You can show a fullscreen anchor behind the modal: if you click it, it will close the modal just like clicking the ‘X’.


      • 10

        Thank you, Karel. Nice technique!

        Now, how would you make it more semantic?

        • 11

          Lucijan Blagonic

          September 29, 2016 2:50 pm

          Interesting idea. I made a small concept that would only use one link and you could close it by clicking on the overlay. It relies on the before/after pseudo elements of the button to simulate the overlay and the icon.

          Check it out:

        • 12

          Interesting idea that got me thinking (and a very good article).

          Here is an example on how to make the modal overlay act as a closing link. Basically, I’m using a single closing link in the modal (which is more semantic), and styling before/after pseudo classes to display the icon and modal overlay. The idea could be improved further (positioning of the modal overlay e.g. but this is just a quick demo of the idea).

          Check it out:

  5. 13

    All your iframes are broken on my OnePlus two marshmallow 6

  6. 15

    Nice UX Marko!

    This image is broken on the App section-

  7. 17

    Thanks for this great case study, Marko.
    Good to see other pros stick to progressive enhancement.

  8. 18

    Alexander Pershin

    October 5, 2016 3:08 pm

    Hi, Marko

    Nice article, thanks.

    There is a problem with a modal window. This code breaks the accessiblity of content:

    /* E.g. URL */
    #modal-contact { display: none; }
    /* E.g. URL */
    #modal-contact:target { display: block; }

    It hides the modal window in every browser (even in very old one). But makes this modal visible only in modern browsers (supporting :target). You got a bunch of old browsers with hidden content.

    It`s one of progressive enhancement problems in CSS. The layers of code should be atomic even in CSS. One solution is to wrap both CSS-rules inside @supports. Or use another method, like «checkbox+label».

  9. 19

    Quite insightful and helpful blog! Thanks for sharing it!

  10. 20

    Hemang Rindani

    January 5, 2017 4:07 pm

    Progressive enhancement is the technique of building websites with strong foundations so that it’s accessible to the wide range of browsing situations, say from desktop to workstations to mobile and tablets. We carry out Progressive enhancement by markingup well structured HTML for flexibility, enhancing look and feel progressively and making it compatible with browsers and then implement rich Javascript.

    Understand more about it with few examples.


↑ Back to top