Step-by-Step Guide: Creating a Template for Vue 3, Vuetify 3, and Storybook
Peshang Hiwa
Posted on February 25, 2023
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.
BASH
npm init vue@latest
Integrating Vuetify 3 in to the project
BASH
npm i vuetify
npm i @mdi/font
Create a new file in src/plugins/vuetify.ts
, include all the vuetify configurations there and then export it.
TYPESCRIPT
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,
},
},
});
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");
Installing and configuring storybook
npx storybook init
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:
HTML
<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>
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:
Typescript
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,
};
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>
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.
TYPESCRIPT
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 }),
}
);
};
};
Now that our decorator is done, we only have to import it in the preview.js
file as below:
TYPESCRIPT
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];
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
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
Posted on February 25, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.