Building a Beautiful Quote Generator with Vue.js and Tailwind CSS

Building a Beautiful Quote Generator with Vue.js and Tailwind CSS

Today we will build a basic Vue JS app that displays a random quote from an API on a button click. As a bonus, we will also implement functionality that also changes the background of the app whenever we generate a new quote.


Creating a new Vue JS project

I used Vite to bootstrap my project. You can also use vite or vue cli or anything.

After creating a new vue project using vite, you can add tailwind to the project by following this beautiful guide from the official Tailwind CSS documentation.

Once done with setting up the project, you can remove the unnecessary boilerplate code and start with a blank App.vue file.

This a fairly simple project so we can code the entire project within the App.vue file itself. However, if you want to break down the app into components feel free to do so.


Creating the template for the app

<template>
<!-- Container -->
  <div
    class="
      min-w-screen min-h-screen
      bg-cover bg-no-repeat bg-fixed bg-center
      flex
      items-center
      justify-center
      px-5
      py-5
    "
    :style="{backgroundImage: `url(https://images.unsplash.com/photo-1497561813398-8fcc7a37b567?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80)`}"
  >
  <!-- Quote section -->
    <div
      class="
        w-full
        mx-auto
        rounded-lg
        bg-white
        shadow-lg
        px-5
        pt-5
        pb-10
        text-gray-800
      "
      style="max-width: 500px"
    >
      <div class="w-full pb-5 "></div>
      <div class="w-full mb-4">
        <div class="text-3xl text-indigo-500 text-left leading-tight h-3"></div>
        <!-- Actual Quote -->
        <p class="text-sm text-gray-600 text-center px-5">
Setting an example is not the main means of influencing another, it is the only means.
        </p>
        <div
          class="text-3xl text-indigo-500 text-right leading-tight h-3 -mt-3"
        ></div>
      </div>
      <div class="w-full">
        <!-- Quote Author -->
        <p class="text-md text-indigo-500 font-bold text-center">
          Albert Einstein
        </p>
      </div>
      <!-- Button -->
      <div class="w-full flex justify-center items-center">
      <div
        class="
          mt-4
          inline-flex
          items-center
          justify-center
          px-5
          py-3
          text-base
          font-medium
          text-center text-indigo-100
          border border-indigo-500
          rounded-lg
          shadow-sm
          cursor-pointer
          hover:text-white
          bg-gradient-to-br
          from-purple-500
          via-indigo-500
          to-indigo-500
        "
      >
        <svg
          class="w-5 h-5 mr-2"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            stroke-width="2"
            d="M13 10V3L4 14h7v7l9-11h-7z"
          ></path>
        </svg>
        <span class="relative">Generate Quote</span>
      </div>
      </div>
    </div>
  </div>
</template>

The template of our app is as above. What we have done is we have created a container (a flex-container). The flex container is given a backgroundImage property using v-bind :style . The background image property is set with a URL of an image from Unsplash which will be displayed initially. Later we dynamically change the image upon a button click along with our quote.

Within the container, we also have a Quote section that contains quote content part, the quote author. Below this, we also have a custom button that says Generate Quote with a margin top of around 1rem (mt-4)

Initially we have hardcoded the quote and the author part, but it will be replaced with the quote from an external API once we fetch it.

If we see we are using flex to create our layout, All we have done is use flex properties to center our sections and layout all the parts of our app in a beautiful manner.

If you have followed till now then the app should look something like this...

Since we have used Tailwind to style our app, it will be responsive. You can check it yourselves😎.


Let's Generate a Quote!

As you might have seen we have hard-coded the quote and author in the quote section in our markdown. So let's implement the functionality to fetch and display the quote.

We will be fetching a random quote from https://api.quotable.io/random API call to this endpoint will return an object with content and author fields.

So within the script tag of our App.vue we will have the following code

export default {
  data() {
    return {
      quote: {
        content: "",
        author: "",
      },
    };
  },
}

The above code is used to declare the variables that will be used in the program. These variables will be updated when we fetch the quote.

Now that we have set up the variables, we will create a method that will make an API call to the API endpoint and updates these two state variable we have set above.

  methods: {
    async fetchQuote() {
      const data = await fetch("https://api.quotable.io/random").then((res) =>
        res.json()
      );
      this.quote = {
        content: data.content,
        author: data.author,
      };
    },
  },

Here we are using native fetch API to fetch the quote. Our method fetchQuote() is an asynchronous function which means that it returns a Promise. The await keyword is used to wait for the Promise. It makes the code wait until the Promise returns a result.

Let's append this method to our Generate Quote button using @click event handler so that when we click the button we call this method, which in turn fetches the quote.

<!-- Button -->
      <div class="w-full flex justify-center items-center">
      <div
        @click="fetchQuote()"   
        class="
          mt-4
          inline-flex
          items-center
          justify-center
          .....
          .....

Once we obtain the result we update the empty content & author variable with actual quote content and its author that we have fetched.

Now that we have fetched the actual quote, we can remove the hardcoded quote and author from our template and replace it with our quote contentand author variable as shown below.

<!-- Actual Quote -->
        <p class="text-sm text-gray-600 text-center px-5">
            {{ quote.content }}
        </p>
        <p class="text-md text-indigo-500 font-bold text-center">
          {{ quote.author }}
        </p>

Now if you run the app locally, you'll see something like this...

Although we have implemented the functionality, we have a potential problem in our app i.e, on the initial page load of our app since our variable content and author is set to empty string initially, When we run our app we neither will see a quote nor an author.

This happens because we are only fetching quotes upon button click. So till we won't click the button that says Generate Quote we won't make any API call thus we won't fetch and display any quote. In order to fix this we must make use of the created() hook.

The created() hook allows you to add code that is run if the Vue instance is created.

You cannot do any DOM manipulations because DOM has not been mounted at this stage. created() is called earlier in order to trigger actions like data fetching from the API backend.

So let's add our fetchQuote() method using created() hook as shown below...

created() {
    this.fetchQuote();
  },

Now you can see, whenever our Vue Instance is created, we call this fetchQuote() method explicitly which fetches the Quote and updates our variables and will be rendered in the DOM so that we have a quote initially, and upon button click, we generate another random quote.

So this is how we can create a dynamic quote generator using Vue JS and Tailwind CSS.


✨Bonus Section

However, if you want to change the backgroundImage of the app with every button click then you can do so...

We will use unspash's https://source.unsplash.com/random/ endpoint to get a random image.

Similar to what we did with generating quote, initially we will have a initial reactive state instance variable called imageUrl which will be empty initially.

  data() {
    return {
      imageUrl: "",
      quote: {
        content: "",
        author: "",
      },
    };
  },

Then we will create a new method called getImage() which as the name suggests gets a random image from unspash using native fetch API and updates our imgUrl variable.

  methods: {
    async getImage() {
      this.imageUrl = await fetch(
        "https://source.unsplash.com/random/?motivation"
      ).then((res) => res.url);
    },
    async fetchQuote() {
        ...
        ...
      };
    },
  },

Quick Tip :- You can also pass a query option to our unsplash base URL as I've added in my code block above where we specifically mentioned unsplash to return image related to motivation. You can also pass whatever option you want like blurred, productivity, etc...

"https://source.unsplash.com/random/?motivation"

Now we will append this method to our button that says generate quote. So whenever this button is clicked we will call both fetchQuote() & getImage() methods.

<!-- Button -->
      <div class="w-full flex justify-center items-center">
      <div
      @click="fetchQuote();
              getImage();"
        class="...
               ...

However, we have a final step remaining which is to conditionally bind this imgUrl variable to our v-bind style: in our template above.

    :style="[
      imageUrl
        ? { backgroundImage: `url(${imageUrl})` }
        : {
            backgroundImage: `url(https://images.unsplash.com/photo-1497561813398-8fcc7a37b567?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80)`,
          },
    ]"

So we have a quote generator app that initially will have this static image with a random quote and upon the click event will generate a new quote as well as change the background of our app🥳.

Code is available here --> Git Repo

Live preview of the app we've built --> Click!