Telerik blogs
VueT2 Light_1200x303

Designing intuitive user interfaces is an everyday challenge. I'm sharing tips for Vue and Nuxt that you can use to improve your application's UX.

As you may already know, πŸ˜… designing intuitive user interfaces is an everyday challenge. Still, one of the aspects I’ve enjoyed since I started coding is figuring out all those tiny little changes that can improve the user experience. πŸ•΅πŸΌ‍♀️

For instance, because my designs are component-oriented, it feels like having a superpower πŸ’₯ to be able to toggle on and off a feature with just the switch of a prop.

This is why I’m writing this article, to share a few tips (10 to be exact) for Vue and Nuxt that you can use to improve your application’s UX. I mean, let’s face it, 🀷‍♀️ it helps to be able to overdeliver on your work, especially when you are a freelancer.

So, let’s dive in, and let us enjoy some UX goodness.

Tip #1: Use Placeholders to Make Your Users Feel Like Your Component Is Loading Faster (Vue & Nuxt)

Vue Placeholder shows ghosted back wireframe-style layout with a shifting gray gradient to show it's loading

There are two great packages you can use to extend your components when it comes to placeholders.

Both of them will be a great fit to set up any kind of placeholder you need, ☝️ but you may have to tweak them a little bit if you are looking to design more complex animations.

Here is how you can toggle a placeholder inside a component when the loading prop switch from “true” to “false.”

    <content-placeholders v-if="loading">
      <content-placeholders-heading :img="true" />
      <content-placeholders-text :lines="3" />

    <div v-else>My Component</div>

export default {
  props: {
    loading: {
      type: Boolean,
      default: false

⚠️ Note: If you are using Nuxt, make sure that you are familiar with the fetch function and $fetchState.pending. It will allow you to display a placeholder when fetch is being called on client-side.

Tip #2: Add a Specific Prop to Reverse Your Buttons (Vue & Nuxt)

When you have to display two buttons next to one another, it is obviously important for the user to be able to differentiate them. One beginner’s mistake πŸ˜” is to use the same color for both. πŸ˜• Another UX mistake is to use two completely different colors that have no relationship whatsoever with each other.

Reverse Buttons: Two side-by-side buttons on a purple page, one with a white background and a lighter purple text, the other with just a white outline and the purple of the page as the fill using white text.

πŸ€“ What I recommend to do instead is to create a reverse state for each color with a reverse prop. This will allow you to put the focus on the filled button while keeping your interfaces beautiful.

Tip #3: Add a Loading State to Your Button (Vue & Nuxt)

Your buttons are usually triggering asynchronous calls to your server. This means that the interface will be paused until a response is received. ⏱ Because the waiting time varies depending on someone’s network speed, you have to let the user know when the query is successful. βœ…

πŸ‘ One quick and easy way you to do so is to provide a loading prop to your button component so it displays a spinner inside.

Button Loading State: A small circle grows and shrinks on top of a button.

⚠️ Note: If you want your button to keep the same width while transitioning from one state to another, you can switch the content opacity to zero when the button is loading and center the spinner using absolute positioning.

Tip #4: Prefetch Some of Your Data Server-side (Nuxt)

One common mistake I have seen people make when they build their authentication system by themselves (i.e., not using nuxt-auth) is πŸ™„ to load the user data client-side.

The issue with this process is that when you refresh the page, you must wait for the server response βŒ›οΈ before displaying the elements that are used only when logged. πŸ˜₯

To avoid this transition on the client, β˜οΈπŸ€“ a solution is to fetch the data server-side with nuxtServerInit action.

From the documentation:

If the action nuxtServerInit is defined in the store and the mode is universal, Nuxt.js will call it with the context (only from the server-side). It’s useful when we have some data on the server we want to give directly to the client-side.

export const actions = {
   * Called server-side at initialization
   * @param {Object} context
   * @param {Object} req
  async nuxtServerInit(context, { req }) {
    // Set token from cookies when defined and fetch user
    if (req && req.headers.cookie) {
      const cookie = cookieparser.parse(req.headers.cookie)

      if (cookie.token) {
        try {
          await context.commit('auth/setToken', { token: cookie.token }, { root: true })

          await context.dispatch('user/fetchUser', {}, { root: true })
        } catch (error) {
          return Promise.reject(error)
      } else {
        await context.commit('user/setUser', { user: false }, { root: true })

Now, when you refresh the page, the user is already in the store, and all the components needed when logged are instantly displayed. πŸ§™‍♀️

Tip #5: Use TailwindCSS to Get a Clean, Responsive Website with Sizzy (Vue & Nuxt)

This tip is more about productivity, but it was vital to add it to this article. These two tools made my daily work easier and helped me design better interfaces. πŸ‘©πŸ½‍πŸ’»βœ¨

If you haven’t heard about TailwindCSS, I recommend that you take a look at this CSS framework. Especially if you have to design responsive interfaces, its main advantage comes with how you can apply a specific CSS property for a particular breakpoint. πŸ‘ Watch the TailwindCSS screencasts if you want to dig into this framework. Trust me, you will not regret it.

🎬 Screencasts: Designing with Tailwind CSS.

In combination with the Sizzy browser, you will see how each interface is rendering for each specific device. πŸš€ That’s powerful!

Sizzy shows various screen views, each labeled with its device, like iPhone 8, Galaxy S9, iPad Air, etc. We see nine different views in this screenshot.

Tip #6: Interfaces with Infinite Load Should Always Come with a “Scroll to Top” Button πŸ” (Vue & Nuxt)

Scrolling is usually faster than clicking (especially on mobile devices). This is probably why infinite loading has become such a popular pattern to display additional data on a web page.

But keep in mind that when a user has been scrolling for a few minutes, he may just want to access something at the top of the page. To ease this process for him, you can simply display a “Scroll to top” button ⬆️ after the third page has been reached.

πŸ‘‰πŸΌ Here is a Vue package you can use to implement this behavior: vue-backtotop.

Tip #7: Lazy Load Your Images and Scripts (Vue & Nuxt)

You are probably already lazy loading your images. But if that’s not the case, here is how you can do it without using any external library:

<img src="image.png" loading="lazy" alt="" width="200" height="200">

⚠️ Keep in mind that this method only works for modern browsers.

To speed up your application even more, you can also lazy load your third-party scripts. Here is a simple function that includes a promise you can use to achieve this:

 * Lazy load an external script
 * @param {String} slug
 * @return {Object} script
export const loadScript = function(src, force = false) {
  return new Promise(function(resolve, reject) {
        let existingEl = document.querySelector(`script[src="${src}"]`);

        if (existingEl && !force) {
            if (existingEl.classList.contains("is-loading")) {
                existingEl.addEventListener("load", resolve);
                existingEl.addEventListener("error", reject);
                existingEl.addEventListener("abort", reject);
            } else {

        const el = document.createElement("script");

        el.type = "text/javascript";
        el.async = true;
        el.src = src;

        el.addEventListener("load", () => {

        el.addEventListener("error", reject);
        el.addEventListener("abort", reject);


Now, πŸ€“ we can load any script like this:

// With a promise
loadScript("myscript.js").then(() => {
   console.log("script loaded")

// Or with async await
await loadScript("myscript.js")
console.log("script loaded")

Of course, if the script already exists in the page, it will load a second time.

Tip #8: Help Users Know Where They Are on Your Website 🧭: Use Breadcrumbs (Vue & Nuxt)

Breadcrumb showing Home > Technologies > Shopify

If your application has categories with multiple levels, you should take a moment to see where you can implement a breadcrumb. This will help each visitor know where they are inside the website and go back to a higher level when they need to.

The code below is using TailwindCSS. You can use it and adapt it to your needs πŸ˜‹.

  <div class="flex items-center px-4 py-2 mb-6 bg-gray-200 border rounded select-none">
    <div v-for="(item, index) in itemsWithHome" :key="item.label" class="flex items-center">
        :is=" ? 'nuxt-link' : 'div'"
            'font-bold': index + 1 !== itemsWithHome.length,
            underline: index + 1 === itemsWithHome.length
        class="text-xs uppercase shadow-none last:mr-0"
        {{ item.label }}

      <span class="mx-1">></span>

export default {
  computed: {
    itemsWithHome() {
      const routeItems = this.$route.path.split('/').filter((item) => item)
      const nonRouteItems = ['lists']

      const items = [
          label: 'Home',
          link: {
            name: 'homepage'

      // Build breadcrumb items with links
      routeItems.forEach((routeItem, index) => {
        if (index === 0) {
            label: routeItem,
            to: {
              name: routeItem
        } else {
          const item = {
            label: this.$filters.unslugify(routeItem)

          if (!nonRouteItems.includes(routeItem)) {
   = {
              name: this.$,
              params: this.$route.params


      return items

Tip #9: Use Accordions to Compress Lengthy Content for Small Devices (Vue & Nuxt)

Accordions allow you to hide or expand the content of a block of text

Accordions are probably your best choice πŸ‘Œ when it comes to organizing multiple pieces of information in interfaces designed for small devices.

They reduce the amount of content displayed, and people are already familiar with the fact that they only have to click on them to know more about the section.

If you’re not using them in your app yet and/or don’t know how to implement them, πŸ‘‰πŸΌ check this Codepen example.

Tip #10: Allow the User to Share πŸ“’ Pages with Filtered Content by Adding Filter Parameters to Your URLs (Vue & Nuxt)

This is a neat trick I learned from my husband when he was implementing a segmentation system for his CRM. 🌝 When a user starts using filters, you can save the parameters in the URL, which gives you something that looks like this:,fr,equals,or&,behance_page,facebook_group,equals,and&page=1

So when the user needs to share a filtered search result with a coworker, she only needs to send him the link (without having to give him any extra info) 😎 and the application can reconstruct it exactly as it was by reading every condition inside the query parameters.

Do this, and you will make your user’s life easier when they want to share a specific page with their colleagues or save it for later. 😜

πŸ™ŒπŸΌ That’s all for me today!

I hope that these tips will help you improve your app’s user experience. If you continuously try to make your UX better, your customers will use your app and love you even more with every incremental improvement you make! πŸ–€ πŸ”œ πŸ’²

You can comment below this article if you want to share one of your UX tips. You can also reach me out on Twitter @RifkiNada.

About the Author

Nada Rifki

Nada is a JavaScript developer who likes to play with UI components to create interfaces with great UX. She specializes in Vue/Nuxt, and loves sharing anything and everything that could help her fellow frontend web developers. Nada also dabbles in digital marketing, dance and Chinese. You can reach her on Twitter @RifkiNadaor come on her website,

Related Posts


Comments are disabled in preview mode.