TUTORIAL: Jekyll Search with Non-JavaScript/CSS Fallback

By Kai Gittens tags:  reading time: 15 min
image for the 'TUTORIAL: Jekyll Search with Non-JavaScript/CSS Fallback' post

(August: 2025 Author's note: as of this note, this post is 12 years old. It doesn't use Jekyll, doesn't use any of the Tipue search functionality and there are noted links that are broken. But the process for detecting whether or not CSS is disabled that's outlined below is pretty cool!!! Check that out if you like!!! -k)

Jekyll is a static site generator: it creates static sites instead of database-driven ones. This means that it doesn't contain the site search functionality commonly bundled into CMS software like WordPress and Drupal.

A common solution to this problem is to use some sort of JavaScript-based search functionality, but this won't work if the end-user has disabled JS in their browser. This tutorial shows you how to not only add JS-powered search functionality to your static site, but also how to create a fallback search method for situations where either JavaScript or, as an added bonus, CSS is disabled.

Table of Contents

  1. The Three Steps We Need To Take
  2. One Assumption...More Notes
  3. The Fallback Search Code
  4. A Very Quick Tipue Walkthrough
  5. Step 1: Add the JavaScript Detection & Fallback Code to HTML Pages
  6. Step 2: Dynamically Create the JS-powered Search Functionality
  7. Step 3: Use JavaScript to Detect if CSS is Disabled
  8. More Notes
  9. Conclusion

The Three Steps We Need To Take

There are three steps we need to take to achieve our goal:

  1. Add the JavaScript Detection & Fallback Code to HTML Pages: These pages will link to JavaScript and CSS files working together to check for the presence of JavaScript, and will also contain our fallback search functionality, courtesy of Google.

  2. Dynamically Create the JS-powered Search Functionality: We'll create it off-DOM first, then load it onto the pages next.

  3. Use JavaScript to Detect if CSS is Disabled: With JavaScript, we'll create code that provides the Google fallback search for times when CSS is disabled, but JavaScript is not. Because CSS is disabled, the Google page that returns the search results will not look pretty, but the results will be returned nonetheless.

One Assumption...More Notes

Since we're talking about creating search for static sites, the only assumption I'm making is that you have either Jekyll or some other static site software installed on your machine, and that you use it regularly.

Some notes...

The Fallback Search Code

Before we get to the three steps, we need to understand some things about the fallback code...

Our fallback search functionality comes from Google Custom Search Engine (CSE), which comes in three versions at the time of this post. This tutorial uses the oldest version as it best suits our needs and although it's old, it's still widely in use, particularly on sites using the Octopress framework for Jekyll.

The second version is very similar to the first version and works when JavaScript is disabled, but adds an extra click-through to the user experience when compared to the first one...not what I wanted. The third version is what Google currently recommends but is a pure JavaScript version that won't work if JS is disabled...also, not what I wanted.

Let's look at version 1 of the code we'll be using:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Search For Content</title>
</head>
<body>
  <h1>Search here...</h1>
  <form action="http://google.com/search" method="get">
    <fieldset role="search">
      <!-- change the "value" attribute below to point to your site -->
      <input type="hidden" name="q" value="site:yoursite.com"/>
      <input class="search" type="text" name="q" results="0" placeholder="Search"/>
    </fieldset>
  </form>
</body>
</html>

Note the setting of the value attribute in the first <input> tag: "yoursite.com". You would need to change this to whatever your site URL is. Once that's done, any searches entered into this searchbox will return the results for that site, and return them inside a standard Google search results page.

To be honest: if you want search engine functionality on your static site, you really can just apply a Google CSE solution and move on. The reason I went beyond Google CSE was because I wanted to deliver a certain experience on kaidez.com: when people performed a search on my site, I wanted them to stay on my site.

None of the current CSE solutions do this, so I went with Tipue while using version 1 for my fallback code.

A Very Quick Tipue Walkthrough

Very quick...

Tipue is a jQuery plugin that provides static search. The search results must be returned to a properly-configured search.html page.

Along with the search.html page, Tipue also needs five JS files to work and they should be listed in the following order:

  1. the core jQuery library.
  2. tipuesearch_content.js, which contains the searchable site data that's returned to search.html
  3. tipuesearch_set.js, which filters words and phrases in the search results.
  4. js/tipuesearch.min.js, which is the core Tipue code.
  5. a file containing the executable Tipue code that returns search results to search.html...this code will be placed in a file called js/scripts.js and we'll discuss it shortly.

Step 1: Add the JavaScript Detection & Fallback Code to HTML Pages

We need to create web pages that include references to both a .css file and two more .js files. We're not going to review any of the Tipue-related files we just discussed as they don't play a role in the JS detection process, but these other files do play a role:

Let's review these files...

index.html



<html lang="en" class="no-js">
<head>
  <meta charset="UTF-8">
  <title>Search For Content</title>
  <link href="http://fonts.googleapis.com/css?family=Open+Sans:300,400" rel="stylesheet">
  <link href="css/tipuesearch.css" rel="stylesheet">
  <link href="css/styles.css" rel="stylesheet">
  <script src="js/detect.js"></script>
</head>
<body>
  <div id="container" class="containerClass">
  <h1>Search here...</h1>

    <!-- Tipue Search box will go here -->
    <div id="searchbox"> </div>

    <!-- Google CSE search box starts here -->
    <div id="no-js-searchbox">
      <form action="http://google.com/search" method="get">
        <fieldset role="search">
          <!-- change the "value" attribute below to point to your site -->
          <input type="hidden" name="q" value="site:yoursite.com"/>
          <input class="search" type="text" name="q" results="0" placeholder="Search"/>
        </fieldset>
      </form>
    </div>
    <!-- Google CSE search box ends here -->

  </div>

  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
  <script>window.jQuery || document.write('<script src="js/libs/jquery-1.10.2.min.js"><\/script>')</script>
  <script src="js/tipuesearch_content.js"></script>
  <script src="js/tipuesearch_set.js"></script>
  <script src="js/tipuesearch.min.js"></script>
  <script src="js/scripts.js"></script>

</body>
</html>

The key parts of the file:

search.html



<html lang="en" class="no-js">
<head>
  <meta charset="UTF-8">
  <title>Search Results</title>
  <link href="http://fonts.googleapis.com/css?family=Open+Sans:300,400" rel="stylesheet">
  <link href="css/tipuesearch.css" rel="stylesheet">
  <link href="css/styles.css" rel="stylesheet">
  <script src="js/detect.js"></script>
</head>
<body>
  <div id="container" class="containerClass">
  <h1>Search Results</h1>

    <!-- Tipue Search box will go here -->
    <div id="searchbox"> </div>

    <!-- Google CSE search box starts here -->
    <div id="no-js-searchbox">
      <form action="http://google.com/search" method="get">
        <fieldset role="search">
          <!-- change the "value" attribute below to point to your site -->
          <input type="hidden" name="q" value="site:yoursite.com"/>
          <input class="search" type="text" name="q" results="0" placeholder="Search"/>
        </fieldset>
      </form>
    </div>
    <!-- Google CSE search box ends here -->

  </div>

  <div id="tipue_search_content"></div>

  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
  <script>window.jQuery || document.write('<script src="js/libs/jquery-1.10.2.min.js"><\/script>')</script>
  <script src="js/tipuesearch_content.js"></script>
  <script src="js/tipuesearch_set.js"></script>
  <script src="js/tipuesearch.min.js"></script>
  <script src="js/scripts.js"></script>

</body>
</html>

Again, search.html is the page where Tipue returns the search results. It's similar to index.html but has an extra tag: <div id="tipue_search_content"></div>. This is the page element where Tipue places the search results.

css/styles.css


.js #no-js-searchbox {
  display: none;
}

/*
 * The code below is just applying styles and has nothing to do with
 * the CSS/JS detection process.
 */
body {
  font: 12px/1.7 'open sans', sans-serif;
}

h1 {
  text-align: center;
}

form, p {
  text-align: center;
}

.containerClass {
  margin: 0 auto;
  width: auto;
}

As mentioned, the .js #no-js-searchbox is the key class here...the rest of the styles just add generic styling. .js #no-js-searchbox will hide the Google CSE search only when JavaScript is enabled...let's start breaking down how that's done.

js/detect.js


// This code is stolen from Modernizr so if Modernizr is already on
// your web page, don't use this part of the code.

// This code is one file and not inline because it's a best practice as
// per the Content Security Policy (CSP). Mike West breaks CSP down really
// well over at: http://bit.ly/KzGWUZ. Also make sure to read the CSP
// W3C spec at: http://bit.ly/vCQbiW

var docElement = document.documentElement;
docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + ('js');

A very important piece of our code:

var docElement is storing a short-hand reference to document.documentElement, which is a reference to our page's <html> tag. docElement.className is a reference to the tag's only class: no-js.

A regular expression search is run against docElement.className when the page loads. When the search finds the text "no-js", it replaces it with the text "js".

Our HTML tag will now look like this:

<html lang="en" class="js">

Because of the class name change and because of CSS descendant selectors, the .js #no-js-searchbox selector will work and set our Google CSE search box to display:none. But if JavaScript is disabled, the regular expression search won't happen.

If the regular expression search doesn't happen, it means that the no-js class will remain in the <html> tag and that the .js #no-js-searchbox selector cannot be applied; in other words, if JavaScript is disabled, the Google CSE search box will be visible. This is exactly what we want.

As the comments say, this code is currently built into Modernizr so if Modernizr's already on your page, you don't need this code.

Also, it's suggested that Modernizr be placed in the <head> tag so it can do work before the DOM starts constructing the page content. We're treating this piece of code the same way for the same reason.

And as noted in the comments, this code is placed in its own JS file instead of inline because it's a best practice as per the Content Security Policy (CSP) that's starting to gain a consensus. Mike West's Content Security Policy article on HTML5 Rocks breaks it down really well but you should still read the W3C's Content Security Policy spec sooner than later.

js/scripts.js


(function(){

  // The Tipue-powered code that returns search results to
  // "search.html".
  $(function() {
    $('#tipue_search_input').tipuesearch();
  });

})();

This file is just running our Tipue search code, which has absolutely nothing to do with the JS/CSS detection. But again, it's where we're going to add our remaining code and it's the only file we're really going to talk for the rest of this post.

At this point we've established the basic structure for our pages as well as our system for detecting whether or not JavaScript is enabled. We also understand that if JavaScript is disabled, the Google search box will be visible.

Let's now go to step two and build our Tipue search functionality.

Step 2: Dynamically Create the JS-powered Search Functionality

We now need to create the Tipue search box off-DOM with JavaScript, then load it onto the page. Specifically, we need to create a form on both pages that looks like this:


<form action="search.html" role="search">
  <input type="text" name="q" id="tipue_search_input" placeholder="Search...">
  <input type="submit" value="Search">
</form>

We'll add this code to our already-existing js/scripts.js file so it will now look like this:


(function(){

  // Tipue code
  $(function() {
    $('#tipue_search_input').tipuesearch();
  });

  var loadSearchBox = document.getElementById("searchbox"),
    frag = document.createDocumentFragment(),
    form = document.createElement("form"),
    searchTextBox = document.createElement("input"),
    searchButton = document.createElement("input");

  form.action = "search.html";
  form.setAttribute("role", "search");

  searchTextBox.type = "text";
  searchTextBox.name = "q";
  searchTextBox.id = "tipue_search_input";
  searchTextBox.placeholder = "Search...";

  searchButton.type = "submit";
  searchButton.value = "Search";

  form.appendChild(searchTextBox);
  form.appendChild(searchButton);

  frag.appendChild(form);

  loadSearchBox.appendChild(frag);

})();

We already know what the Tipue code is doing so let's look at the "build the search box code"...


var loadSearchBox = document.getElementById("searchbox"),
  frag = document.createDocumentFragment(),
  form = document.createElement("form"),
  searchTextBox = document.createElement("input"),
  searchButton = document.createElement("input");

We're using a single var pattern to create five variables:


form.action = "search.html";
form.setAttribute("role", "search");

We have to apply attributes to our three newly-created page elements, starting with the <form> tag. We're setting the tag's action attribute to search.html (which targets Tipue's search results page) and setting its role attribute to search (which is good from a web semantics standpoint).

The end result of all this is a form tag which, from a code perspective, looks like this: <form action="search.html" role="search"></form>


searchTextBox.type = "text";
searchTextBox.name = "q";
searchTextBox.id = "tipue_search_input";
searchTextBox.placeholder = "Search...";

We next have to apply attributes to our first input element. The main thing we have to do is turn it into a text box: that happens with the searchTextBox.type = "text" line of code.

Both searchTextBox.name = "q" and searchTextBox.id = "tipue_search_input" are what's being done as per the Tipue documentation, so let's never change that code. searchTextBox.placeholder = "Search..." can be changed and even removed if you want to...I would change it but never remove it as it's a great visual cue for end-users.

The end result of all this is an input tag which, from a code perspective, looks like this: <input type="text" name="q" id="tipue_search_input" placeholder="Search...">


searchButton.type = "submit";
searchButton.value = "Search";

We next have to apply attributes to our second input element. The main thing we have to do is turn it into a submit button: that happens with the searchButton.type = "submit" line of code.

The value of searchButton.value can be anything you want it to be.

The end result of all this is a submit button which, from a code perspective, looks like this: <input type="submit" value="Search">

Now we have to arrange all the page elements we created off-DOM.


form.appendChild(searchTextBox);
form.appendChild(searchButton);

Since the <form> tag should contain our two <input> tags, the <form> tag is viewed as the "parent element" and each input tag is viewed as a "child element". We can add each child to the inside of the parent using the appendChild() method.

Because the search box (which is represented by the searchTextBox variable) is appended first, it will appear in our code before the search button (which is represented by the searchButton variable).

At this point, we've constructed our search box the way we want to and it exists off-DOM in the browser memory...the time has come to load it onto the pages.


frag.appendChild(form);

We load our <form> tag and all of its contents into our document fragment, which is curently represented in the variable list above by our frag variable.


loadSearchBox.appendChild(frag);

Since frag contains our complete <form> code, JavaScript takes it and loads it into the <div id="searchbox"></div> element already on our web pages, targeting it with the loadSearchBox variable.

At this point, this is what the JavaScript (not CSS) detection process looks like:

  1. One of our HTML pages load.

  2. If the page loads in a browser where JavaScript is enabled, js/detect.js changes the no-js class in the <html> tag to js.

  3. The above class name change means that the .js #no-js-searchbox selector can apply a display:none setting to the Google CSE search box currently on the page and hide it.

  4. js/scripts.js runs the code that builds the Tipue search box off-DOM and loads it onto the page.

  5. If the page loads in a browser where JavaScript is disabled, steps 2, 3 and 4 can't happen because they need JavaScript to run. So the Google CSE search box won't be set to display:none and be completely visible, giving our end-users a search option. Also, since JS is disabled, the Tipue search box won't be built.

(Shameless self-promotion: this part of the tutorial focused on dynamically constructing page elements off-DOM....if you want to learn more about this, check out my off-DOM screencast tutorial.)

Step 3: Use JavaScript to Detect if CSS is Disabled

The code in Step 2 works well if either JavaScript is disabled or if both JavaScript and CSS are disabled. But if just CSS is disabled, things fall apart.

If just CSS is disabled, JavaScript will still change the no-js class name in our <html> tag to js. The point of this code was to allow the invocation of the .js #no-js-searchbox selector so we can hide our Google CSE search box.

But if just CSS is disabled in a browser, it renders all custom styles useless and allows only the the browser's default styling to render. This means that .js #no-js-searchbox will be ignored and the CSE box will be visible.

And since JavaScript is enabled in this case, the Tipue search box will load onto our page, meaning every page will have two search boxes. That's bad so we need to detect if CSS is enabled, making sure that the Tipue search box isn't built if CSS is disabled. Which is fine because, as mentioned in the paragraph above, the Google search box will be visible if CSS is disabled, giving our end-users a search option in every situation.

Someone by the name of "Kethinov" shared a very cool trick to use JavaScript to detect if CSS is enabled in a browser over on the SitePoint forum. I made a few syntax changes but remain quite loyal to his ridiculously clever code.

Let's update our already-existing js/scripts.js file so it looks like this:


(function(){

  // Tipue code
  $(function() {
    $('#tipue_search_input').tipuesearch();
  });

  // Variables used throughout the code
  var loadMenu,
    isCSSDisabled,
    testCSS,
    currStyle;

  // Function that builds the Tipue search box
  loadMenu = function() {
    var loadSearchBox = document.getElementById("searchbox"),
      frag = document.createDocumentFragment(),
      form = document.createElement("form"),
      searchTextBox = document.createElement("input"),
      searchButton = document.createElement("input");

    form.action = "search.html";
    form.setAttribute("role", "search");

    searchTextBox.type = "text";
    searchTextBox.name = "q";
    searchTextBox.id = "tipue_search_input";
    searchTextBox.placeholder = "Search...";

    searchButton.type = "submit";
    searchButton.value = "Search";

    form.appendChild(searchTextBox);
    form.appendChild(searchButton);

    frag.appendChild(form);

    loadSearchBox.appendChild(frag);

  }

  // Start detecting if CSS is enabled or disabled
  isCSSDisabled = false;

  testCSS = document.createElement('div');

  testCSS.style.position = 'absolute';

  document.getElementsByTagName('body')[0].appendChild(testCSS);

  if (testCSS.currentStyle) {
    currStyle = testCSS.currentStyle['position'];
  } else if (window.getComputedStyle) {
      currStyle = document.defaultView.getComputedStyle(testCSS, null).getPropertyValue('position');
  }

  isCSSDisabled = (currStyle === 'static') ? true : false;

  document.getElementsByTagName('body')[0].removeChild(testCSS);

  if (isCSSDisabled === false) {
    loadMenu();
  } else {
    return false;
  }

})();

We already know what the Tipue code is doing so let's look at the "CSS enabled/disabled" detection code...


var loadMenu,
  isCSSDisabled,
  testCSS,
  currstyle;

We're creating four new variables using the single var pattern again. We'll give them value as we go along, starting with the loadMenu variable.


loadMenu = function() {
...
}

The code we used to build the Tipue search box is now a function stored in a variable called loadMenu. The code hasn't changed so it's not displayed here, but it's important to understand that while the previous version of the code ran immediately, this new version isn't doing that. This code will now not run until we tell it to.


isCSSDisabled = false;

The isCSSDisabled variable that we created earlier is a Boolean type variable, meaning it has a value of either true or false. We're setting it to false.

isCSSDisabled will be the variable that will tell us whether or not CSS is enabled in the browser....remember that.


testCSS = document.createElement('div');

The testCSS variable that we created earlier is storing a reference to a <div> tag created with the createElement() method.


testCSS.style.position = 'absolute';

The value of a <div> tag's position property is static by default...let's change the position property of our testCSS div to absolute.


document.getElementsByTagName('body')[0].appendChild(testCSS);

Load the testCSS div onto our web page so we can properly detect it in a browser. Do this by finding the <body> tag and place the testCSS div inside of it. It will be placed just above the closing <body> tag because we're using appendChild().


if (testCSS.currentStyle) {
  currStyle = testCSS.currentStyle['position'];
} else if (window.getComputedStyle) {
    currStyle = document.defaultView.getComputedStyle(testCSS, null).getPropertyValue('position');
}

We need to find the value of our testCSS div's position property and place it inside the currStyle variable we created earlier. oldIE refers to this property one way...the other browsers refer to it another way. So we need to use a little feature detection here.

If our testCSS div has a currentStyle property attached to it, we're in oldIE. So use currentStyle to find the position property and store it's value inside currStyle.

But if the window object has a getComputedStyle() method attached to it, we're in a browser other than oldIE. So use getComputedStyle() to find the position property and store its value inside currStyle.


isCSSDisabled = (currStyle === 'static') ? true : false;

Our isCSSDisabled variable runs a quick ternary operation, which is a short-hand conditional check. As previously mentioned, isCSSDisabled will tell us whether or not CSS is enabled in the browser and does so as follows...

isCSSDisabled checks the value of our currStyle variable which, again, is storing the value of our testCSS div's position property. If it still has its default static setting, it means our browser is ignoring the absolute property we applied...which would only happen if CSS was disabled in a browser. So isCSSDisabled will equal true.

But if testCSS div's position property is set to anything else but static (such as the absolute setting we gave it earlier), then CSS must be enabled. So isCSSDisabled will equal false.


document.getElementsByTagName('body')[0].removeChild(testCSS);

Our test is done so we don't need testCSS div on our page anymore...let's remove it.


if (isCSSDisabled === false) {
  loadMenu();
} else {
  return false;
}

Our testCSS div may be gone but our isCSSDisabled variable is still around, and we can check its value. And if isCSSDisabled is set to false, it means that CSS is not disabled so it's safe to run our loadMenu() function to build the Tipue search box. But in any other situation, such as isCSSDisabled being set to true, don't do anything else and, just to play it safe, do absolutely nothing by performing a basic return false.

More Notes

While this code works, there are a few things to keep in mind:

Conclusion

There are lots of search options for static sites generators like Jekyll...we just need to try things and then implement, test, then deploy them. This is just one thing...I'm sure that there are more and I haven't found them. Please share these things in the comments if you like.