Create multipurpose buttons with Nuxt & Tailwind

Tailwind CSS
Nuxt
Aug 28, 2021

I'll show you the concept of creating a reusable button component for your next Nuxt project with Tailwind CSS in just a few steps.

This post assumes that you have already integrated Tailwind CSS into your project. If not, look into the official tutorial.

Goals to achieve

  • One component should cover all necessary scenarios for its purpose

  • Single file maintenance

  • Intuitive development

Basis

First, we create a new component with the name BaseButton.vue. The "Base" symbolizes a reusable base component. This should help us to identify these kinds of components later in the template and also simplify the implementation by using short and understandable names.

Vue
BaseButton.vue
<template>
  <button @click="$emit('click')">
    <slot></slot>
  </button>
</template>

Make it multipurpose

First, we can implement the possibility to use the button as a link. For this, we use dynamic components. With the to prop we want the component to become <nuxt-link>and with href a <a> tag.

Vue
BaseButton.vue
<template>
  <component
    :is="to ? 'nuxt-link' : href ? 'a' : 'button'"
    :to="to"
    :href="href"
    @click="$emit('click')"
  >
    <slot></slot>
  </component>
</template>

<script>
  export default {
    props: {
      to: {
        type: String,
        default: null
      },
      href: {
        type: String,
        default: null
      }
    }
  };
</script> 

Now the default is a button that can be controlled with the @click event. If you specify the to or href attribute, the button will automatically be a link.

Vue
BaseButton.vue
// Button
<BaseButton
  @click="onClickButton"
>Button</BaseButton>

// Button as nuxt-link
<BaseButton
  to="/en/blog"
  rel="next"
>Link</BaseButton>

// Button as anchor
<BaseButton 
  href="https://thenextbit.de/en/blog"
  target="_blank"
  rel="external"
>Link</BaseButton>

Styles

You should add your base classes for the style, according to your corporate design. All classes that should contribute to changes in color, shape or variant we control via the props and dynamic class bindings.

In the following example, we will control the color of the button through the prop color. We can define the colors we need with computed properties. In this case I have created primary, success and error.

Then we can dynamically bind the classes in the template and give the button a different look depending on the color prop.

Vue
BaseButton.vue
<template>
  <component
    :class="[
      { 'bg-indigo-700': primary },
      { 'bg-green-500': success },
      { 'bg-red-600': error }
    ]"
  >
    <slot></slot>
  </component>
</template>

<script>
  export default {
    props: {
      color: {
        type: String,
        default: 'primary'
      }
    },

    computed: {
      primary() {
        return this.color === 'primary';
      },
      success() {
        return this.color === 'success';
      },
      error() {
        return this.color === 'error';
      }
    }
  };
</script>

Sizes

Similar to the last examples, we can now control sizes and variants through props.

Set the props according to your requirements and bind a class to the respective state. It is recommended to always have a base state that is already active without a prop. An example here could be four different sizes like sm, a default size, lg and xl:

Vue
BaseButton.vue
<script>
  export default {
    props: {
      sm: {
        type: Boolean,
        default: false
      },
      lg: {
        type: Boolean,
        default: false
      },
      xl: {
        type: Boolean,
        default: false
      }
    }
  };
</script>

Slots

You can make your button even more dynamic by adding more slots. An example can be that you want to insert an icon before or after your buttons text.

The following representation is only a simplification. You can of course have wrapper elements with custom styles for the respective slots. But this is to be implemented according to your requirements.

Vue
BaseButton.vue
<template>
  <component 
    class="flex items-center space-x-2"
  >
    <slot name="prepend"></slot>
    <slot name="default"></slot>
    <slot name="append"></slot>
  </component>
</template>

And in use, it can look like this:

Vue
<BaseButton
  color="success"
  to="/contact"
>
  <MailIcon slot="prepend" />
  <span slot="default">contact us</span>
</BaseButton>

More to know

If the <button> tag is the root element in your <BaseButton> component, then the attributes like rel, target and title, which are important for SEO when using links, will automatically apply to the button element. So you don't need to predefine everything in the component props.

You can (and probably should) also dynamically bind and style the disabled and loading states.

The complexity and sophistication of the component will of course depend on your design requirements. Since this post is just to show the rough concepts, this will do for now.

Conclusion

I hope I was able to introduce you to the concepts of how to build reusable and flexible components with Nuxt.js and Tailwind CSS.