Setting up Dark Mode for Nuxt and Storybook via Tailwind CSS

Enable multiple color modes for component design in Storybook, and use color mode-aware components in Nuxt

Setting up Dark Mode for Nuxt and Storybook via Tailwind CSS - Post illustration

This article reviews configuring dark mode via Tailwind CSS for Nuxt and Storybook. The following resources are used:

The companion repository includes the complete configuration of all moving parts, demonstrated via the example of a simple Button component.

Project source code available at...
visini/nuxt-tailwind-storybook-dark-mode

Nuxt Color Mode – Enabling Dark Mode

Color Mode module adds boilerplate-free and effortless color mode switching – including auto detection based on the system's color-mode preferences – to any Nuxt app. See this excellent walkthrough for a more thorough introduction.

Nuxt Color Mode and Tailwind CSS – Dark Mode
Nuxt Color Mode and Tailwind CSS – Dark Mode

Different color modes require different styling. Thus, every component needs to be configured with conditional styling for all supported color modes, which will be applied based on the user's selection.

Nuxt Color Mode and Tailwind CSS – Light Mode
Nuxt Color Mode and Tailwind CSS – Light Mode

In order to define global styles for dark mode, add the respective CSS declarations. For instance, specify white text on dark background for when dark mode is activated by the user:

assets/css/tailwind.css
CSS
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

.dark {
  @apply bg-dark;
  @apply text-white;
}

Tailwind CSS utility classes based on props such as the component's variant can be dynamically computed within buttonClass() – for both light and dark mode. A base class provides fundamental styling scoped to the button component via @apply.

components/Button.vue
Vue
<template>
  <button class="base" :class="buttonClass">
    <slot name="text">{{ text }}</slot>
  </button>
</template>

<script>
export default {
  props: {
    variant: {
      type: String,
      default: "primary",
    },
    square: {
      type: Boolean,
      default: false,
    },
    text: {
      type: String,
      default: "Button",
    },
  },
  computed: {
    buttonClass() {
      const c = [];
      // Square
      if (!this.square) {
        c.push("rounded-md");
      }
      // Variant
      if (this.variant === "primary") {
        c.push("bg-primary-500 hover:bg-primary-600");
        c.push("dark:bg-primary-900 dark-hover:bg-primary-800");
      } else if (this.variant === "secondary") {
        c.push("bg-gray-500 hover:bg-gray-600");
        c.push("dark:bg-gray-700 dark-hover:bg-gray-600");
      }
      return c.join(" ");
    },
  },
};
</script>

<style lang="postcss" scoped>
.base {
  @apply font-bold py-2 px-4 text-white;
}
</style>

Storybook Dark Mode – Component Library

A third-party Dark Mode addon for Storybook allows switching both UI and component view between light and dark mode. Optional: Add argTypes to interact with a component's arguments dynamically via a graphical UI. See Storybook's control addon docs for more information. For instance, switch between rectangular and default (round) buttons by modifying component props via Storybook's UI.

components/Button.stories.js
JavaScript
import Button from "./Button";

export default {
  title: "Button",
  component: Button,
  argTypes: {
    variant: {
      control: {
        type: "select",
        options: ["primary", "secondary"],
      },
      defaultValue: "primary",
    },
    square: { control: "boolean" },
    text: {
      control: "text",
    },
  },
};

export const Template = (arg, { argTypes }) => ({
  components: { Button },
  props: Object.keys(argTypes),
  template: '<Button v-bind="$props" />',
});

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

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

export const Square = Template.bind({});
Square.args = {
  square: true,
};

Adding dark mode support directly within Storybook enables effortless display of stories and components in both color modes – in the same way users would switch between modes – i.e., based on CSS class inheritance: A dark class is added to Dark Mode Plugin for Tailwind CSS to the <head> tag, while Color Mode module for Nuxt adds the dark class to the <html> tag.

Storybook – Dark Mode for both UI and component view
Storybook – Dark Mode for both UI and component view

The top right corner of the center toolbar enables switching between light and dark mode. This will switch the UI between color modes, and, as configured in the companion repository, at the same time modify the component view with a specified dark class, effectively simulating an user's color mode preference.

Storybook – Light Mode for both UI and component view
Storybook – Light Mode for both UI and component view

Optional – Tailwind Config Viewer

A useful tool for previewing your Tailwind CSS configuration is Tailwind Config Viewer, which is integrated into Nuxt's Tailwind CSS module since v3.4.0. Simply launch Nuxt in dev mode and access /_tailwind/ in your browser.

Tailwind Config Viewer
Tailwind Config Viewer

Conclusion

I hope this article proves insightful to anyone wanting to set up Nuxt with Tailwind CSS and Storybook. As shown here, supporting dark mode for component prototyping and design, and subsequent integration in your frontend app requires some additional tooling.

Project source code available at...
visini/nuxt-tailwind-storybook-dark-mode

See the companion repository for specific configuration of all moving parts. Let me know if you have suggestions – I'm curious to learn of alternative approaches!

© 2024 Camillo Visini
Imprint