DEV Community

Peshang Hiwa
Peshang Hiwa

Posted on

Step-by-Step Guide: Creating a Template for Vue 3, Vuetify 3, and Storybook

When working with Vue 3 and Vuetify 3, configuring Storybook 6 can be a challenge. In fact, it's very common to encounter compatibility issues and integration problems between these frameworks. This can be especially frustrating when trying to use configurations from your own project, such as implementing dark mode in to storybook's Vuetify. Fortunately, there are strategies you can use to simplify the process and streamline your workflow.

In this article, I'll walk you through a comprehensive step-by-step guide to creating a Vue 3 project, integrating it with Vuetify 3, and configuring Storybook on top.


Initializing a Vue 3 project and integrate Vuetify 3 in to it.

Let's start by creating a new Vue 3 project and clean up the built in template inside it.

npm init vue@latest
Enter fullscreen mode Exit fullscreen mode

Integrating Vuetify 3 in to the project

npm i vuetify
npm i @mdi/font
Enter fullscreen mode Exit fullscreen mode

Create a new file in src/plugins/vuetify.ts, include all the vuetify configurations there and then export it.

import { createVuetify } from "vuetify";
import type { ThemeDefinition } from "vuetify";
import "vuetify/styles";
import "@mdi/font/css/materialdesignicons.css";

import * as components from "vuetify/components";
import * as directives from "vuetify/directives";

const lightTheme: ThemeDefinition = {
  dark: false,
  colors: {
    primary: "#d35400",
    secondary: "#8e44ad",
    background: "#ecf0f1",
    error: "#c0392b",
    info: "#2980b9",
    success: "#27ae60",
    warning: "#f1c40f",
  },
};

const darkTheme: ThemeDefinition = {
  dark: true,
  colors: {
    primary: "#d35400",
    secondary: "#8e44ad",
    background: "#2f3640",
    error: "#c0392b",
    info: "#2980b9",
    success: "#27ae60",
    warning: "#f1c40f",
  },
};

export default createVuetify({
  components,
  directives,
  theme: {
    defaultTheme: "light",
    themes: {
      light: lightTheme,
      dark: darkTheme,
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

I have configured Light and Dark theme configurations as well.

And finally import and use the configurations in main.ts

import { createApp } from "vue";
import App from "./App.vue";
import vuetify from "./plugins/vuetify";

createApp(App).use(vuetify).mount("#app");
Enter fullscreen mode Exit fullscreen mode

Installing and configuring storybook

npx storybook init
Enter fullscreen mode Exit fullscreen mode

now storybook is installed and configured automatically within the project when the above script is done.

stories and .storybook directories are generated in to the project as well.

stories contains some basic components so that there are some examples to show when running npm run storybook
You can delete this folder as we don't need it, we'll create a custom component manually.

.storybook includes all the configurations for storybook

Creating a custom component and preparing stories for it.

Our component will be a simple button named MyButton inside src/components directory that will have some basic props:

<script setup lang="ts">
import { withDefaults } from "vue";
export type Props = {
  /** Color of the button */
  color?: "primary" | "secondary" | "success" | "info" | "warning" | "error";

  /** To show a loading icon */
  loading?: boolean;

  /** the content of the button */
  content?: string;
};
withDefaults(defineProps<Props>(), {
  loading: false,
  color: "primary",
  content: "Button",
});
</script>

<template>
  <v-btn :color="color" :loading="loading">
    <!-- @slot To put any element inside the button-->
    <slot>
      {{ content }}
    </slot>
  </v-btn>
</template>
Enter fullscreen mode Exit fullscreen mode

Note that the comments in the above example aren't just there for clarification purposes inside our code, but they are being displayed as descriptions for the props and slots by stories automatically.

Now according to our component we'll create a MyButton.stories.ts in the same directory of the component as below:

import MyButton from "./MyButton.vue";

export default {
  title: "MyButton",
  component: MyButton,
  argTypes: {
    color: {
      control: {
        options: [
          "primary",
          "secondary",
          "success",
          "info",
          "warning",
          "error",
        ],
      },
    },
    loading: {
      control: {
        type: "boolean",
      },
    },
    content: {
      control: {
        type: "text",
      },
    },
  },
};

const Template = (args) => ({
  components: { MyButton },
  setup() {
    return { args };
  },
  template: '<MyButton v-bind="args" content="click me"/>',
});

export const Primary = Template.bind({});
Primary.args = {
  color: "primary",
};

export const Secondary = Template.bind({});
Secondary.args = {
  color: "secondary",
};

export const WithLoading = Template.bind({});
WithLoading.args = {
  color: "error",
  loading: true,
};
Enter fullscreen mode Exit fullscreen mode

before running storybook we'll need to add some configurations to .storybook directory as well.

Storybook configurations for vuetify

In order for vuetify to work inside storybook, some extra configurations are required.

First we need to have our own wrapper to run storybook on top it, for this purpose we'll create a file named storyWrapper.vue inside .storybook and it'll include all the HTML skeletons that are required for vuetify to run on it such as v-app:

<template>
  <v-app :theme="themeName">
    <v-main>
      <slot name="story"></slot>
    </v-main>
  </v-app>
</template>

<script>
export default {
  props: {
    themeName: String,
  },
};
</script>
Enter fullscreen mode Exit fullscreen mode

Now we need to create a custom decorator for storybook so that it knows how to replace it's default wrapper with our own custom one, our decorator is named withVuetifyTheme.decorator.js inside .storybook folder.
Any custom configurations related to vuetify can be added in to this file, in our case it'll be only configuring themes.

Then we need to import and render the wrapper in an h function iside our decorator file.

import { h } from "vue";
import StoryWrapper from "./storyWrapper.vue";

export const DEFAULT_THEME = "light";

export const withVuetifyTheme = (storyFn, context) => {
  // Pull our global theme variable, fallback to DEFAULT_THEME
  const themeName = context.globals.theme || DEFAULT_THEME;
  const story = storyFn();

  return () => {
    return h(
      StoryWrapper,
      // give themeName to StoryWrapper as a prop
      { themeName },
      {
        story: () => h(story, { ...context.args }),
      }
    );
  };
};
Enter fullscreen mode Exit fullscreen mode

Now that our decorator is done, we only have to import it in the preview.js file as below:

import { withVuetifyTheme } from "./withVuetifyTheme.decorator";
import { app } from "@storybook/vue3";
import vuetify from "../src/plugins/vuetify";

app.use(vuetify);

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    expanded: true,
    sort: "requiredFirst",
  },
  docs: {
    inlineStories: false,
  },
};

export const globalTypes = {
  theme: {
    name: "vuetify.theme.defaultTheme",
    description: "Global theme for components",
    toolbar: {
      icon: "paintbrush",
      // Array of plain string values or MenuItem shape (see below)
      items: [
        { value: "light", title: "Light", left: "🌞" },
        { value: "dark", title: "Dark", left: "🌛" },
      ],
      // Change title based on selected value
      dynamicTitle: true,
    },
  },
};

export const decorators = [withVuetifyTheme];
Enter fullscreen mode Exit fullscreen mode

Note that we have created a theme globalType to render a button on the toolbar of storybook in order to switch between themes for vuetify.

And now our storybook is configured with vuetify and is ready to be run:

npm run storybook
Enter fullscreen mode Exit fullscreen mode

Image

As it's clear on the toolbar you can as well switch between themes.

The repository for this project is available here, you can directly download/use the template for your project.

Thanks for reading

Top comments (0)