The term “mustard cutting” in web design comes from developers at the BBC who wanted to serve different experiences of their site to different browsers on different devices. They were specifically trying to avoid User Agent sniffing:
But these days, with such a long tail of user agents now accessing the site, this becomes a fruitless exercise.
Instead, they used feature detection. Modernizr is the classic (awesome) example of that, but ala carte feature tests can be quite small and simple all by themselves. This is the logic the BBC used to determine if a browser cut the mustard or not:
if('querySelector' in document
&& 'localStorage' in window
&& 'addEventListener' in window) {
// bootstrap the javascript application
}
If that logic failed, the website still loads what they called a core experience. If that logic passed, additional resources would load for an enhanced experience.
Pretty cool.
Loading additional CSS and JavaScript is fairly easy
There are various ways to do it, typically involving an XHR for the resource. Filament Group has some very tiny, focused scripts just for this: loadCSS and loadJS.
Loading a bit of extra HTML via XHR is similarly easy. But…
It’s too hard to load an entirely different document client-side
Say you don’t need just a bit of extra CSS, scripts, or a bit of HTML. What you want is an entirely different document.
Your “core experience” and “enhanced experience” are entirely different sets of HTML, CSS, and JavaScript. Trying to do this client-side would mean trying to load a super bare-bones document, then trying to essentially re-create how the browser parser works. First you mustard-cut, then XHR for the right set of HTML you need, then either drop it into the DOM, or perhaps wait until you’ve XHR’d for the CSS so you don’t get a flash of unstyled document. Then XHR for all the scripts, and make sure that you execute them in order (tricky).
Hard to pull off.
Allow the server to serve the correct document based on mustard-cutting information
If the server knew the results of your mustard-cut, you could avoid all that trouble and serve the correct document right off the bat.
That’s the whole point of client-side mustard-cutting: it can only be done on the client. But… you could save that data to a cookie, and cookies can be read by the server.
It you had a cookie you could count on, you could do something like this in the routing of your site:
<?php
// This is just a fake routing/controller kinda setup.
if (isset($_COOKIE["mustard"])) {
// Enhanced experience
if ($_COOKIE["mustard"] == true) {
include_once("enhanced.php");
// Core experience
} else {
include_once("core.php");
}
// No cookie = core experience
} else {
include_once("core.php");
}
?>
What happens on the first page view though?
That’s the tricky part here. That cookie won’t exist on the first page view. You could just let subsequent pages serve the correct experience, but that’s not likely to be acceptable.
Here comes the most controversial part: if you don’t have the cookie but can tell the browser supports them and they are enabled, you refresh the page.
Refresh the page?! Are you kidding?
Totally reasonable questions: How can a refresh possibly be a good user experience? Aren’t refreshes slow? Couldn’t you get caught in a refresh loop?
I think all of these things can be addressed.
At the very top of the document, if that cookie is not there and the browser does support cookies:
- Mustard-cut and save the data to a cookie with JavaScript
- If the mustard-cut data tells you you should be loading a different document: halt the page from loading/doing anything else (
window.stop();
) and refresh (location.reload(true);
).
Upon refresh, the cookie will be there for the server.
It all happens so fast when it’s the very first thing a document does that I find it barely noticeable. This is what we’re doing for the editor page on CodePen, see:

The trick to avoiding a refresh loop is to only execute that part of the JavaScript if you’re sure cookies are supported and enabled.
The mustard-cutting script
Here’s a mustard-cut that only tests the screen width. Bear in mind a mustard-cut could be anything you want it to be that you can test client-side.
(function() {
// If the browser supports cookies and they are enabled
if (navigator.cookieEnabled) {
// Set the cookie for 3 days
var date = new Date();
date.setTime(date.getTime() + (3 * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
// This is where we're setting the mustard cutting information.
// In this case we're just setting screen width, but it could
// be anything. Think http://modernizr.com/
document.cookie = "screen-width=" + window.outerWidth + expires + "; path=/";
/*
Only refresh if the WRONG template loads.
Since we're defaulting to a small screen,
and we know if this script is running the
cookie wasn't present on this page load,
we should refresh if the screen is wider
than 700.
This needs to be kept in sync with the server
side distinction
*/
if (window.outerWidth > 700) {
// Halt the browser from loading/doing anything else.
window.stop();
// Reload the page, because the cookie will now be
// set and the server can use it.
location.reload(true);
}
}
}());
In fact, we don’t have to load that script at all if the cookie is already there, since if it is, we know the correct page has loaded already.
<?php
// Run this script as high up the page as you can,
// but only if the cookie isn't already present.
if (isset($_COOKIE["screen-width"]) == 0) { ?>
<script src="mobile-mustard.js"></script>
<?php } ?>
Possible Scenarios
- The normal first time visitor: No cookie is present. Mustard-cut script will run and refresh the page quickly. They will get correct document based on cut.
- The repeat visitor: Cookie is already present. They will get correct document based on cut.
- Visitor with incorrect cookie: Perhaps they have a desktop browser but it was very narrow when the page loaded the first time, but they have since widened it. We can detect that with a CSS @media query and offer a link to correct the problem (see demo).
- Visitor with cookies off: We serve our choice of documents. Could potentially be wrong. Serve the best likely case based on data.
- Visitor in which JavaScript doesn’t run: We serve our choice of documents. Could potentially be wrong. Serve the best likely case based on data.
Possible Problems
For the record, I’m not saying this is the best possible solution to this problem. In fact, if you’re in a situation where you can do everything purely client-side, that’s probably better.
Here’s some potential problems with this solution:
- Perhaps the reload is slower than I think it is. I didn’t do any testing of super old / super slow devices like I probably should have.
- HTML caching could be a problem. I experienced this first hand when building the demo on a site that was using that method. The server serves a cached document, which then is determined to be the incorrect one and refreshed, causing the dreaded refresh loop. Solution: don’t HTML cache this page, or redirect to subdomains based on the cut.
- Security settings that prevent server-side access to cookies created by JavaScript. If you can’t control that, that would be a problem.
I do use this technique in production though and haven’t had an issue in a lot of months, so I’m pretty happy with it.
Demo and Repo
Here’s a demo and the code is up on GitHub if you spot any fouls.
Also, I thought client hints was supposed to be the savior here, but I’m just not sure anymore where it fits into this situation.
Refreshing the page is not fast, or at least not fast enough, IMO. Many times I’ve visited Codepen and saw enough of the UI that I could begin to move my mouse toward a button… on only to have the rug pulled out from under me as the page reloaded. It would be one thing if the screen remained blank white while the test was run, but seeing a fully styled site and then having it taken away isn’t too grand.
This is with Chrome on a reasonably powered Windows PC.
I’d love to get a quick screencast if that if possible. If we’re talking desktop Chrome, it should never refresh at all, because we actually assume desktop and only redirect on small screens. I don’t doubt that it’s happening, I just need more information, as I spend like all day every day working on CodePen and I’ve literally never seen that.
I’m on chrome desktop on a pretty dated windows pc and this is not happening for me
I thinks its bad to store screen width information in a longlived cookie. as it might change due to device orientation. resolution changes. or external monitors connected to a laptop.
The demo confronts this problem by detecting (through media queries) if they wrong template has loaded and offering a link to correct it. You could even auto-correct it if you wanted to, that’s kind of the beauty of all this.
I guess the GIF is how Codepen is supposed to look on mobile. Well, for me at least, it doesn’t. I am using Android 4.2.1 on one high quality but at honest price smartphone (read Chinese but not Apple) with a 1080×1920 screen.
I see the desktop version all the time. Is it something related to the high density display?
That 1080 width would be why. If you do a window.outerWidth from the browser on that phone, it tells you “1080”? That’s pretty wide!
Our mustard-cut at CodePen is admittedly primitive. I wouldn’t write off this technique based on that though, that’s just a simple example. The technique is doing what it’s supposed to be doing – and you can get as sophisticated as you like with the mustard cut.
Thanks Chris,.. I dig this approach.. gets ya thinkin’. Have you ran into any SEO/Analytics related issues?
Even if you don’t get around to doing it soon / ever, it would be interesting to see your notes when evaluating progressive enhancement and responsive design for CodePen.io.
What makes the desired functionality and experience best achieved with the adaptive/mustard approach here?
What web technology is missing, or what makes a responsive CodePen.io infeasible?
I see two problems with this:
What version are you going to serve to search engines? No cookies, so just the “basic” one I assume – will that be enough for them to get the whole content to index? (Maybe we could go back to UA sniffing for search engine bots …)
Or, the other way around: Might they punish us for presenting them with the “full” version – but then delivering less content to certain devices, under the same URL?
I have my doubts about the reliability of only querying
navigator.cookieEnabled
and taking that as a sign that it will work. You already mentioned “security settings” that could be a problem in this regard, but I think there might be a lot of potential scenarios where this can fail.My browser f.i. has a setting letting me confirm every single cookie – using that,
navigator.cookieEnabled
reports true, but I can still decline the cookie. Also there might be firewalls/proxies filtering out cookies from requests send to the server, or browser extensions that manipulate how the browser reacts to cookies or whatnavigator.cookieEnabled
reports … so I think, the possibility of ending up in a redirect loop is very real. (I would perhaps at least include a test that reads a cookie value that was just set back viadocument.cookie
– to at least get some more certainty about the client-side part of the issue.)You say you hadn’t had “an issue in a lot of months” – but would you actually know if a user of your site had problems with it? (I don’t know what possible measures or hard data you based this assumption/conclusion on – just saying, that not getting any complains does not necessarily mean no problems.)
[Hey, this looked nicer in the preview – your Markdown parser working as it is supposed to? I used numbers to create a list with two items, but it’s gone …]
Analytics would play into this more, but if most users are “enhanced.php” by default (which I would assume the CodePen audience is), you could assume enhanced.php with a “not checked” cookie and use an asynchronous AJAX request so that enhanced.php users are on their way from the get go. Only core.php users would require a refresh from that point once the async request concludes.
Many web apps I’ve seen will do this with a loading bar while they flesh out capabilities of the user, though I’ve never been a huge fan of a initial loading bar on web sites/apps.
Ultimately, whatever is most performant (both Actual and Perceived) for the end user is what’s most important. I honestly haven’t noticed anything on CodePen so whatever you’re using seems to work fine!
Maybe I’m wrong, but why don’t serve the right parts with ajax?
it’s straightminded and easier…
And if you really want to show things to non js people you can show them: a) only the things common to others, b) put what you want inside a noscript tag (never used it from ages…)
Am I wrong?
I’ve already created a sort of it for myself and seems to work (you can use touch events and screensize… but remeber touchevents problems and windows 8 convertibles)