Oliver Davies

Rebuilding Bartik (Drupal’s Default Theme) with Vue.js and Tailwind CSS

20th November 2018

Earlier this week, I built a clone of Drupal’s default theme, Bartik, with Vue.js and Tailwind CSS. You can view the code on GitHub and the site itself on Netlify.

Why build a Bartik clone?

I’m a big fan of utility based styling and Tailwind CSS in particular, I was and originally thinking of a way to more easily integrate Tailwind within Drupal - something like I’ve since done with the Tailwind CSS starter kit theme. Whilst thinking about that, I wondered about doing the opposite - rebuilding Drupal (or Bartik) with Tailwind.

Others including Adam Wathan (one of the creators of Tailwind CSS) have rebuilt existing UIs like Netlify, YouTube, Twitter, Coinbase and Transistor.fm with Tailwind as an opportunity for learning and also to demonstrate using Tailwind - this was my opportunity to do the same.

Whilst Drupal itself has adoped React, I’ve personally been looking into Vue.js and have used it for some small personal projects, including some elements of the site. So I decided to use Vue for the interactive parts of my Bartik clone to create a fully functional clone rather than focussing only on the CSS.

Building a static template with Tailwind

The first stage was to build the desktop version, which was done as a simple HTML file with Tailwind CSS pulled in from it’s CDN. This stage took just over an hour to complete.

As Tailwind was added via a CDN, there was no opportunity to customise it’s configuration, so I needed to use to Tailwind’s default configuration for colours, padding, spacing, fonts, breakpoints etc. The page is built entirely with classes provided by Tailwind and uses no custom CSS, except for one inline style that is used to add the background colour for the Search block, as there wasn’t a suitable Tailwind option.

When I decided that I was going to later add some interactivity onto the mobile navigation menu, the existing code was ported into a new Vue.js application generated by the Vue CLI, with the majority of the markup within a Welcome component. This meant that Tailwind was also added as a dependency with it’s own configuration file, though although I had the opportunity to customise it I decided not to and made no changes to it and continued with the default values.

src/App.vue:

<template>
  <div id="app">
    <Welcome title="Rebuilding Bartik"/>
  </div>
</template>

<script>
import Welcome from './components/Welcome.vue'

export default {
  name: 'app',
  components: {
    Welcome,
  }
}
</script>

<style lang="sass">
@tailwind preflight
@tailwind components
@tailwind utilities
</style>

src/components/Welcome.vue:

<template>
  <div>
    <div class="bg-blue-dark">
      <div class="py-4 text-white">
        <div id="header" class="container mx-auto px-4 relative">
          <div class="flex flex-col-reverse">
            <div class="flex items-center">
              <img src="img/logo.svg" alt="" class="mr-4">
              <div class="text-2xl">
                <a href="#0">{{ title }}</a>
              </div>
            </div>

            <div class="text-sm flex justify-end">
              <a href="#0">Log in</a>
            </div>
          </div>
        </div>
      </div>

      <main-menu></main-menu>
    </div>

    <div class="bg-white pt-3 pb-4 lg:pb-12">
      <div class="container mx-auto px-4">
        <div class="flex flex-col md:flex-row-reverse md:-mx-8 my-6">
          <div id="main" class="w-full md:w-auto md:flex-1 md:px-6 mb-8 md:mb-0">
            <div class="font-serif">
              <h1 class="font-normal">Welcome to {{ title }}</h1>
              <p>No front page content has been created yet.</p>
              <p>Follow the <a href="#0" class="text-blue-dark hover:text-blue no-underline border-b border-blue border-dotted hover:bg-solid">User Guide</a> to start building your site.</p>
            </div>

            <div class="mt-10">
              <a href="#0">
                <img src="img/feed.svg" alt="">
              </a>
            </div>
          </div>

          <div class="w-full md:w-1/3 lg:w-1/4 flex-none md:px-6">
            <div class="w-full md:w-1/3 lg:w-1/4 flex-none md:px-6">
                <div class="p-4" style="background-color: #f6f6f2">
                  <h2 class="font-serif font-normal text-base text-grey-darkest border-b border-solid border-grey-light mb-3">Search</h2>

                  <div>
                    <form action="#" class="flex">
                      <input type="text" class="border border-solid border-grey p-2 w-full xl:w-auto">

                      <button type="submit" class="bg-grey-light px-3 rounded-full border-b border-solid border-grey-dark ml-2 flex-none" style="background-color: #f0f0f0">
                        <img src="img/loupe.svg" class="block">
                      </button>
                    </form>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div id="footer" class="text-xs text-white">
      <div class="container mx-auto px-4 pt-16 pb-4">
        <div class="border-t border-solid border-grey-darkest pt-6 -mb-6">
          <div class="mb-6">
            <p><a href="#0">Contact</a></p>
          </div>

          <div class="mb-6">
            <p>
              A clone of <a href="https://www.drupal.org">Drupal</a>’s default theme (Bartik).
              Built by <a href="https://www.oliverdavies.uk">Oliver Davies</a>
              using <a href="https://vuejs.org">Vue.js</a>
              and <a href="https://tailwindcss.com">Tailwind CSS</a>.
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import MainMenu from './MainMenu.vue';

export default {
  components: {
      MainMenu,
  },

  props: {
    title: {
      type: String,
      required: true
    }
  }
}
</script>

Making it responsive

The second stage began with making the existing desktop version responsive - particularly making the navigation menu behave and appear differently on mobile and tablet screens, and stacking the main content area and the sidebar on mobile screens. This was all achieved using Tailwind’s responsive variants.

<div class="bg-white pt-3 pb-4 lg:pb-12">
    ...
</div>

In this example, the pb-4 class adds 1rem of bottom padding to the element by default, then increases it to 3rem at large screen sizes due to the lg:pb-12 class.

Adding interactivity

This is how the main navigation menu works on mobile:

The main navigation menu on mobile.

The show and hide text appears next to a hamburger menu, and clicking it toggles the visiblity of the menu links which are stacked below, as well as the wording of the text itself.

The code for this was moved into a separate MainMenu component, which means that it was easier to have dedicated data properties for whether the menu was open or not, as well as computed properties for building the show/hide text. The open value can then be used to apply the appropriate classes to the main menu to toggle it.

I also moved the links into data too - each link is it’s own object with it's title and href values. This means that I can use a v-for directive to loop over the data items and inject dynamic values, removing the duplication of markup which makes the component easier to read and maintain.

src/components/MainMenu.vue:

<template>
  <div>
    <button
      type="button"
      class="w-full p-3 block sm:hidden bg-blue-light text-sm text-grey-darker text-left focus:outline-none"
      @click="open = !open"
    >
      <div class="flex items-center justify-between">
        <div>
          {{ navText }} - Main navigation
        </div>
        <div>
          <img src="img/hamburger.svg" alt="">
        </div>
      </div>
    </button>

    <div class="container mx-auto px-4 sm:block" :class="[ open ? 'block' : 'hidden' ]">
      <div class="mt-2 sm:mt-0">
        <nav class="flex flex-wrap pb-1 md:p-0 -mx-3 sm:-mx-0">
          <div
            class="px-1 sm:pl-0 mb-1 md:mb-0 inline-block w-full sm:w-1/3 md:w-auto"
            :key="link.title"
            v-for="(link, index) in links"
          >
            <a
              class="block text-sm no-underline text-black px-3 py-2 rounded-lg md:rounded-none md:rounded-t-lg sm:text-center"
              :class="[ index == activeTab ? 'bg-white' : 'bg-blue-light hover:bg-white' ]"
              :href="link.href"
            >
              {{ link.title }}
            </a>
          </div>
        </nav>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    data: function () {
      return {
        activeTab: 0,
        open: false,
        links: [
          {
            title: 'Home',
            href: '#0',
          },
          {
            title: 'Drupal',
            href: 'https://www.drupal.org',
          },
          {
            title: 'Vue.js',
            href: 'https://vuejs.org',
          },
          {
            title: 'Tailwind CSS',
            href: 'https://tailwindcss.com',
          },
          {
            title: 'View code on GitHub',
            href: 'https://github.com/opdavies/rebuilding-bartik',
          },
          {
            title: 'Read blog post',
            href: 'https://www.oliverdavies.uk/blog/rebuilding-bartik-with-vuejs-tailwind-css',
          },
        ]
      }
    },

    computed: {
      navText: function () {
        return this.open ? 'Hide' : 'Show';
      }
    }
  }
</script>

The result

The whole task only took around two hours to complete, and although some of the colours and spacings are slightly different due to the decision to stick with the default Tailwind configuration values, I’m happy with the result.

The original version

The original Bartik theme in a new installation of Drupal 8

The Vue.js and Tailwind CSS version

The Vue.js/Tailwind CSS version, hosted on Netlify

Questions? Comments? I’m @opdavies on Twitter, or send me an email.

Tags