loading...
Front-End Foxes

Build a Beautiful Website with VuePress and Tailwind.css

jenlooper profile image Jen Looper Updated on ・11 min read

Recently I've been working on web sites. Big deal, right? As a web developer, it's kind of my thing. For me, though, this is a change in direction. I've been primarily working on mobile apps for the last few years, with the web work being something of an afterthought as I spun up quick marketing web sites to accompany my mobile apps (such as this super basic Wordpress site for my Snappy Squirrel apps and this really old app site for Practice Buddy). Clearly, with relics of quick 'n' dirty web development experiments littering both my app repos and the internet, it's time to level up to create maintainable and sustainable web sites.

It used to be that Wordpress was our savior for this kind of site. It's scalable for both small marketing sites as well as large blogs. But I prefer something a little lighter weight, written in a language I use daily (e.g. not Php), that's a bit easier to customize. So I've experimented over the years with various static site generators, including, most recently, Hexo for my own blog. Unfortunately, jenlooper.com is pretty painful to update, and has a pretty heavy design based on an off-the-shelf Hexo theme. It still seems "Wordpressy" to me, and its design doesn't do a great job at helping a curious user find the content I want to showcase.

Current Web Site

So, thinking as a Vue.js developer, what's the most efficient way to get a website up and running ASAP? VuePress immediately springs to mind. While VuePress is primarily used as a static site generating tool to create nice-looking documentation (an example is VuePress's own site), what if we used it for things like a technical blog, an app marketing site, or any other utility website that we need to get up and running, fast?

Up and Running with Vuepress

After installing VuePress (globally or locally, as per the docs), you can add a few files and, voila, you have a blog or docs of your very own, simply by adding a README.md file in a folder and typing vuepress dev in a terminal from the root. Add a few more files, and you start to have something very decent looking:

Up and running with VuePress

I've created a very simple Vuepress site as shown above, done with a total of two folders and three files:

Simplest Site ever

This extremely simple VuePress site is available here: install VuePress, cd to this repo, and type vuepress dev.

The only problem with VuePress is, similar to Wordpress, web sites generated with these tools tend to look quite boilerplated: "VuePressy", as I like to say. What if we could use VuePress's powerful static site generation AND style a website to look totally custom? Enter Tailwind.css, the new kid on the block enjoyed by web developers for its non-judgmental styling that just works.

Why Tailwind?

Tailwind is a different kind of CSS framework that styles itself "a utility-first CSS framework for rapidly building custom user interfaces." Unlike Vuetify, Bootstrap, Bulma or other major styling frameworks on the market, Tailwind is not about adding components to your web site like a UI kit. It's not opinionated and has no default theme or built-in components. If you want full control over how your web site looks, while also needing to avoid writing all your CSS by hand, Tailwind might work well for you.

NativeScript-Vue.org - a Tailwind-styled website

Bonus - Tailwind.css works on mobile, too, with your NativeScript-Vue mobile app. Stay tuned for a tutorial.

Let's get started on this project by creating a boilerplate VuePress site. You can use the sample I created above as a template.

Install Tailwind.css

Now, it's time to install Tailwind.css. Since Tailwind needs to be installed as an npm package, we're going to need a package.json file in the root. Create one in the root folder of your project, and add the following elements, editing to personalize the project:

{
    "name": "MyBlog",
    "version": "1.0.0",
    "description": "A new Blog",
    "repository": {
        "type": "git",
        "url": "git+https://github.com/your-github-handle/blog.git"
    },
    "keywords": [
        "keywords"
    ],
    "author": "your-name",
    "license": "MIT",
    "bugs": {
        "url": "https://github.com/your-github-handle/blog/issues"
    },
    "homepage": "https://github.com/your-github-handle/blog/#readme",
    "devDependencies": {
        "tailwindcss": "^0.7.3",
        "autoprefixer": "^9.4.2",
        "postcss-import": "^12.0.1",
        "postcss-loader": "^3.0.0"
    }
}

Yarn install or npm install the packages. What's going on here? Well, we added the Tailwind package, and also an autoprefixer, the postcss-import, and the postcss-loader because these packages are necessary for VuePress to consume Tailwind css.

The next step is to create a Tailwind config file. Do this by typing, from the root folder, ./node_modules/.bin/tailwind init tailwind.js. Let's take a look at the file generated by this command. We notice right away that it's really long, over 900 lines. In it are the settings such as default colors that ship with Tailwind. You'll find things such as 'red': '#e3342f'. You use these presets in your css to quickly style elements: <p class="text-red">. Since this file is so long, you might want to delete parts of it to make it a bit lighter weight; customize this file as per your preference. You can install it without the comments, for example, which will shorten it.

Tip: follow these instructions to shrink the file using PurgeCSS. Thanks for the tip, @ohffs !

Next, we need to make these styles able to be picked up by your website. We're going to use this file in the context of VuePress, so let's get VuePress set up for custom theming.

Create a Custom Vuepress Theme

VuePress ships with its own default look-and-feel, and we know we want to override that, so we must create a new folder in .vuepress called theme along with a file called Layout.vue. In that folder add a folder called styles and add a theme.styl file into that folder. Your folder structure now looks like this:

A little more complexity

Why use Stylus via that theme.styl file? You don't have to use Stylus, but it's used in the VuePress docs so I continued using that pattern.

Edit Vuepress to support Tailwind

Now, you need to make Tailwind available for VuePress. There are a couple of things that have to be done to make them play nicely together.

First, in .vuepress/config.js, edit the code to use postcss to import the tailwindcss plugins and config file. The entire config.js file now looks like this:

module.exports = {
    postcss: {
      plugins: [require('tailwindcss')('./tailwind.js'), require('autoprefixer')],
    },
    title: 'Hello VuePress',
    description: 'Just playing around'
  }

Next, edit .vuepress/theme/styles/theme.styl to import the tailwind preflight and utilities classes. Add these two lines to the .styl file:

@tailwind preflight;
@tailwind components;
@tailwind utilities;

Finally, edit .vuepress/theme/Layout.vue so that the new layout file can pick up the new styles:

<template>
<div>
    <p class="text-red">hi</p>
</div>
</template>
<style lang="stylus">
    @import './styles/theme.styl';
</style>

Restart your dev server (Ctrl-C to stop it, then vuepress dev to restart it) and take a look. It's not very pretty, but you can see a distinct style that wasn't there before: a red colored text.

Learn how Tailwind styles your markup by playing a little with this file. If you edit the markup just a bit:

<div class="rounded shadow-lg w-1/2 m-10 p-5 bg-green">
    <p class="text-red text-lg text-center font-sans">I'm a red text!</p>
</div>

You can see a colored card has been added to your webpage:

A card

You added:

  • a container div with several classes that produced a rounded box (rounded)
  • with a large drop shadow (shadow-lg),
  • width set as half the viewport (w-1/2),
  • with margin of 2.5rem all around (m-10) and
  • padding 1.25rem all around (p-5),
  • finishing with a green background (bg-green).

Inside, you styled the text as:

  • colored red (text-red),
  • sized large (text-lg),
  • centered (text-center),
  • with a sans-serif font (font-sans).

Getting an idea how Tailwind works? It gives you a great deal of control over your look and feel, while providing sensible defaults.

This version of the site is available to view here.

Making the Most of VuePress within a Tailwind Context

Now that you have fine control over your styling, you need to start making some architectural decisions. How much of your site will be .vue files, with plain HTML markup and hard-coded text, and how much will be VuePress-friendly markdown? The two architectures can often seem to be antithetical as it seems like an antipattern to style markdown via HTML tags. I find that it helps to think of your Tailwind-styled .vue files as a container or shell for your VuePress markdown. If you are disciplined about keeping all your text in markdown, it will be easier to maintain your blog, especially if you decide to localize your content.

While working on the new version of my web site, I've had to continually make decisions like this. With the goal in mind that we want to keep our text in markdown, and our HTML in .vue files, let's evolve our custom web site theme a little further.

For the next few tips on integrating these two resources, I'm going to turn to my experiences working on rewriting NativeScript-Vue.org. I'm also rebuilding jenlooper.com to look like this:

Home page

Inner page

The Challenge: Multiple Layouts with VuePress and Tailwind

Once you move past the creation of a very simple VuePress site with a few routes and one basic themed layout, you quickly get into the weeds of leveraging the best of VuePress and Tailwind without getting entirely lost. Let's walk through creating a totally custom-looking web site with two different layouts, namely my web site, currently being reconstructed. You can follow along in this project here.

The idea of this personal website is to showcase my projects and provide a space for my articles, which are admittedly scattered around several sites (Telerik Developer Network, NativeScript Blog, Progress Blog, and Dev.to). The initial page will be a showcase of project cards, and inner pages will provide an area for links and various other informational pages. The components can be broken up into at least two layouts - HomeLayout and BlogLayout. In addition, there are some components nested inside those, namely a Cards component and a Nav and Footer. Pretty standard stuff: the HomeLayout will include the Cards, and the BlogLayout will not. The challenge here is to enable markdown to populate all the areas of the website so as to keep that important separation between text and HTML markup.

The first order of business is to create two new folders in .vuepress/theme: components and layouts. Create any new components as .vue files and put them in the components folder, and do the same in the layouts folder. My .vuepress folder's structure now looks like this:

Folder Structure

Then, extend .vuepress/theme/Layout.vue to support multiple layouts. They key is to remove the <Content/> tag from the mandatory Layout.vue file and use a smarter component tag that changes according to the layout specified in markdown frontmatter. The <template> block in the Layout.vue file, then, changes to look like this:

<template>
<div class="wrapper">  
    <div class="markdown-body m-10 rounded"> 
        <Nav/>            
        <component :is="layout"></component>
        <Footer/>  
    </div> 
</div>
</template>

Then, add a <script> block to import the custom components and new layout files. For my site, it looks like this:

<script>
import Nav from './components/Nav.vue';
import Footer from './components/Footer.vue';
import HomeLayout from './layouts/HomeLayout.vue';
import BlogLayout from './layouts/BlogLayout.vue';
export default {
    components: { Nav, Footer, HomeLayout, BlogLayout },
    computed: {
        layout() {
        return this.$page.frontmatter.layout || 'HomeLayout'
        }
  },
}
</script>

The computed property layout() checks a given page's frontmatter for its specified layout, and assigns it accordingly. Now you can start customizing your layouts in a more granular way.

Since we are bypassing VuePress's standard ways of creating layouts via frontmatter, we have to specify the Tailwind styles for each element on the page. That's the price you pay for avoiding boilerplate, but it's worth it!

Build your Layouts

Let's look at the layout I'll use for most inner pages. The BlogLayout.vue page in my site now contains the <Content/> tag expected by VuePress for displaying markdown. In this page, for example, I add a few styles to surround the generated markdown:

    <template>
        <div class="blog-body bg-white rounded m-10 text-blue-darkest">
            <div class="p-10"> 
                <h1 class="pb-5 text-blue-darkest">{{$page.frontmatter.title}}</h1>
                <Content/>
            </div>
        </div>
    </template>
    <script>
    export default {
        name: 'BlogLayout'
    }
    </script> 

VuePress can then find a given route's markdown file - by default, they are presented as a folder plus a README.md file. For example, my About page uses the BlogLayout file for its display. The README.md file in /about just contains a bit of frontmatter and some text:

---
title: About Me
layout: BlogLayout
---    
Welcome! I'm a Google Developer Expert for Web Technologies and a Senior             Developer Advocate at Progress with over 15 years' experience as a web and mobile developer, specializing in creating cross-platform mobile apps. I'm a multilingual multiculturalist with a passion for hardware hacking, mobile apps, Vue.js, machine learning and discovering new things every day. I'm also the founder and CEO of Vue Vixens, an initiative promoting diversity in the Vue.js community. Contact me here or on Twitter @jenlooper.

Our Privacy Policy can be found [here](/privacy-policy)

Looking to contact me? Email me at jen @ ladeezfirstmedia.com. My resume can be found [here](https://standardresume.co/JenLooper).

VuePress lays out the site and Tailwind handles its styling. But what if you aren't quite satisfied, and want to assign styles to the <p> tags generated by VuePress?

You could add HTML tags into your markdown, but remember, we're trying not to do that. Instead, leave the markdown alone, and use Tailwind's @apply directive to assign Tailwind styling to your generated HTML by adding a snippet to your theme.styl file:

    @css {
        .blog-body p {
            @apply .pb-5;
        }
    }

Note, normally you can add @apply to CSS classes without the @css marker but there seems to be an issue with Stylus files, such that @apply is not always used.

Example 1: About

Another example of finding a good balance between using frontmatter and HTML is found in the card interface that I built in .vuepress/theme/components/Cards.vue. Here, since these cards are displayed on the homepage, I used content derived from the root README.md file. Frontmatter is infinitely flexible, so I created a new array of card content in README:

   cards: [
        {'title': 'Vue Vixens', 'image': 'vuevixens_logo.png', 'blurb': 'Vue Vixens offers free workshops and meetups for foxy people who identify as women. Join us at a local skulk or in a tech conference!','link':'https://www.vuevixens.org'},
        {'title': 'Elocute', 'image': 'elocute_logo.png', 'blurb': 'Elocute is a mobile and web app designed to replace the language lab. Students and teachers of second languages, rejoice!','link':'http://www.elocute.me'},
       ...
    ]

To display this content, I looped through the array in Cards.vue, picking out each element and surrounding it with styles:

    <template>
    <div class="flex flex-wrap pt-5 m-2 justify-center"> 
        <div v-for="item in $page.frontmatter.cards" class="w-1/4 m-2 cursor-pointer bg-white max-w-sm rounded shadow-lg text-center" @click="goToRoute(item.link)">
            <img class="pt-5" :src="'./images/'+item.image+''" :alt="item.title">
            <div class="px-6 py-2">  
                <div class="text-blue-darkest font-bold text-xl mb-2">{{item.title}}</div>
                    <p class="text-blue-darker">
                        {{item.blurb}}
                    </p>
                </div>  
        </div>   
    </div>
    </template>

Example 2: Nav

You aren't constrained to using page-level markdown content; for my Nav.vue navigation component, I turned to my site-level config.js file which contains a Nav object that I leverage to build the universal navigation:

    <span v-for="item in $site.themeConfig.nav" >
        <a :href="item.link" class="block m-4 lg:inline-block lg:mt-0 no-underline text-white hover:text-orange uppercase">
          {{item.text}}
        </a>
    </span>

Conclusion

Updating my site from now on is going to be a dream. No more excuses about maintainability and brittle design! Once your layouts are in place, and you have decided on the content within your markdown files, you have in hand a very solid architecture to build beautiful, scalable, easy-to-maintain websites. Enjoy, and add your tips and tricks on marrying VuePress and Tailwind below.

Posted on by:

jenlooper profile

Jen Looper

@jenlooper

Jen Looper is a Google Developer Expert and a Cloud Advocate Lead at Microsoft with over 20 years' experience as a web and mobile developer, specializing in creating cross-platform web & mobile apps.

Front-End Foxes

Front-End Foxes are people who identify as women and who want to learn front-end technologies to make websites and mobile apps

Discussion

markdown guide
 

Hi Jen,

Under Edit Vuepress to support Tailwind you have:

First, in .vuepress/theme/config.js, edit the code to use postcss to import the tailwindcss plugins and config file.

I think that should be just .vuepress/config.js.

 

ah! you are right. Editing...

 

This is a great article! Thanks, Jen, I just started using tailwind. It's really amazing but I feel like I need to take some CSS refresher classes so I can understand the basics again to use it right.

 

This is a good point. I found myself hanging out a lot in the inspector, reminding myself of weird things you have to do to make your background image behave. Once you remember the css, then you find the abstraction in Tailwind and can move on. Eventually I am assuming I'm going to have some tried and true designs I can just spin up for the various small websites I need. :)

 

How do you feel about using this for a large project ? I would love to see the performance on it vs vanilla css.

I think it will do really well. The key would be to shrink down that tailwind css file as much as you can, and even minimize it if possible. In terms of Vue.js itself, it has very good performance. I'm going to run our sites through lighthouse and try to make them as performant as possible. You can follow our progress at nativescript-vue.org, which is already a Tailwind.css site, but with a custom static site genertor, to see how moving to VuePress will impact it.

I've used tailwind a bit and usually run it through the PurgeCSS pipeline to cut down the size.

It pretty much just does a grep for every tailwind class in your vue/js/html files and if there is no match - it removes it from the css file. You can over-ride it if you're doing some fancy inline-template/variable-substitution stuff too :-)

It's especially nice as you don't need to change the tailwind config at all while you are developing - so you don't find yourself deleting a load of stuff then thinking 'damn, I wish I had those orange background colours after all...' ;-)

This is a super tip. I'll edit the article accordingly and credit you! Thank you!

Goodness - it's like christmas has come early :-D Thank you! :-D

 

Very useful article! Have you tried Gridsome? If not, you should give it a shot. I would love to read your thoughts regarding which use cases are best suited for one or another.

 

I haven’t, it’s be interesting to take a look.

 

The thing is that I'm not sure it's possible to build a multisection page where the contents are splitted in several markdown files with VuePress. That's not its main goal I guess.

VuePress (in my understanding) is meant for documentation websites whereas Gridsome can accommodate any (Markdown-based or Vue-based) static sites so I think Gridsome might be better for your use case.

However, I haven't used both (other than a small start with Gridsome) so I can't say where VuePress is more suited than Gridsome or Nuxt; but I'll love to read about that from someone who knows.

Lucky you! I wrote a nice article on this. Jokes aside, VuePress can be extended to cover many use cases, as can Gridsome. I had a pretty good experience with VuePress so will stick with it. One thing I wouldn't recommend is Hexo. For Nuxt, I'd use it for sites that have more sophisticated needs (such as vuevixens.org).

Would you recommend VuePress for a portfolio website (like this one) and/or a company one with a portal (like spiritcartel.com)?

hm, for the first site, there's a lot of custom styling, and VuePress might honestly get in the way of that. SpiritCartel seems pretty complex...honestly I might go for Nuxt on those sites.

Okay, thanks for the insight. I'll have a go at Nuxt.

 

Just what I was looking for!
But the problem now is that the build time is 10x more.
It only took 1-2s to build before, now it takes 10-12s by adding tailwind

Is there a way to cache the tailwind file?
I think it is being generated on each build in the dev server.

Update

Found a fix!

Instead of importing it in the style tag, import it in the script tag as the tailwindcss page specifies.
This saved my time from 10s to 0.6s!

Add the following line to your script tag

import './styles/tailwind.css';
 

Hi Jen,

I didn't read thru all the comments but there is an important correction needed:

...a static site generating tool to create nice-looking documentation (an example is VuePress's own site)...

Actually Vue (and Vuepress) does not use Vuepress for their documentation/sites; They use Hexo. I know, odd... but look at the repos - They even mention it in several places and all of Vue's docs are opensource (except vuepress) running on Hexo.

 

Hold on a second.

I didn't say the Vue.js docs are built with Vuepress (it's well known that they aren't...yet). But the Vuepress docs site does, as I stated. The Vuepress docs are here: github.com/vuejs/vuepress/tree/0.x..., and you can contribute as per usual. You can see the .vuepress file right in the expected place.

github.com/vuejs/vuepress/blob/0.x... - the scripts don't lie

 

Awesome article! I understand losing some of the boilerplate functionality of vuepress is necessary to implement custom styles but is there a way in which I can still utilise the boilerplate sidebar functionality? For instance when creating markdown files the headings would automatically create sidebar navigation.

 

Hey Jen, nice article!
What's the local() method used to specify the font family?

Thanks

 

Can you show me where it is? thanks!

 

Thanks a lot Jen! This post helped me a lot. I documented my learnings as well in this post:

amalytix.com/en/vuepress-tailwindc...

I hope it helps someone!

 

Love the combination between VuePress and Tailwind. Great article!

 

Thanks Jen,
Just wondering if you considered Vuetify, as it's specifically aimed at Vue ?
vuetifyjs.com/en/

TIA, Dave

 

Hi, yes, big fan of Vuetify. We use it for vuevixens.org. For this project, I want total control of my look-and-feel and I don’t need components, so Tailwind made more sense. Depends on your needs :)