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.
<template>
<div>
<div class="bg-blue-dark">
<div class="py-4 text-white">
<div id="header" class="container relative px-4 mx-auto">
<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"></a>
</div>
</div>
<div class="flex justify-end text-sm">
<a href="#0">Log in</a>
</div>
</div>
</div>
</div>
<main-menu></main-menu>
</div>
<div class="pt-3 pb-4 bg-white lg:pb-12">
<div class="container px-4 mx-auto">
<div class="flex flex-col my-6 md:flex-row-reverse md:-mx-8">
<div id="main" class="mb-8 w-full md:flex-1 md:px-6 md:mb-0 md:w-auto">
<div class="font-serif">
<h1 class="font-normal">Welcome to </h1>
<p>No front page content has been created yet.</p>
<p>Follow the <a href="#0" class="no-underline border-b border-blue-600 border-dotted hover:text-blue-600 text-blue-dark 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="flex-none w-full md:px-6 md:w-1/3 lg:w-1/4">
<div class="flex-none w-full md:px-6 md:w-1/3 lg:w-1/4">
<div class="p-4" style="background-color: #f6f6f2">
<h2 class="mb-3 font-serif text-base font-normal text-gray-900 border-b border-gray-300 border-solid">Search</h2>
<div>
<form action="#" class="flex">
<input type="text" class="p-2 w-full border border-solid xl:w-auto border-gray">
<button type="submit" class="flex-none px-3 ml-2 bg-gray-300 rounded-full border-b border-gray-600 border-solid" 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 px-4 pt-16 pb-4 mx-auto">
<div class="pt-6 -mb-6 border-t border-gray-900 border-solid">
<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>
````
</div>
## 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.
```html
<div class="pt-3 pb-4 bg-white 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.](/images/blog/rebuilding-bartik-vue-tailwind/rebuilt-mobile.png)
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`:
<div v-pre markdown="1">
```vue-html
<template>
<div>
<button
type="button"
class="block p-3 w-full text-sm text-left text-gray-800 sm:hidden focus:outline-none bg-blue-light"
@click="open = !open"
>
<div class="flex justify-between items-center">
<div>
- Main navigation
</div>
<div>
<img src="img/hamburger.svg" alt="">
</div>
</div>
</button>
<div class="container px-4 mx-auto sm:block" :class="[ open ?'block' : 'hidden' ]">
<div class="mt-2 sm:mt-0">
<nav class="flex flex-wrap pb-1 -mx-3 sm:-mx-0 md:p-0">
<div
class="inline-block px-1 mb-1 w-full sm:pl-0 sm:w-1/3 md:mb-0 md:w-auto"
:key="link.title"
v-for="(link, index) in links"
>
<a
class="block py-2 px-3 text-sm text-black no-underline rounded-lg sm:text-center md:rounded-none md:rounded-t-lg"
:class="[ index == ? activeTab'bg-white' : 'bg-blue-light hover:bg-white' ]"
:href="link.href"
>
</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 Vue.js and Tailwind CSS version
I’ve also made some additional changes since this version, which are described in this follow-up post.
Was this interesting?
About me
I'm an Acquia-certified Drupal Triple Expert with 17 years of experience, an open-source software maintainer and Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.