Advanced

Unplugin Jade Garden

Learn about the unplugin-jade-garden plugin and how it is used to generate CSS with Tailwind CSS.

The unplugin-jade-garden plugin is a build tool plugin designed to bridge the gap between your jade-garden configurations and your final CSS output, especially when using Tailwind CSS. It works by processing your cva and sva style configurations at build time and automatically generates CSS stylesheets containing Tailwind's @apply directives or returns the style configurations.

This automation eliminates the need for manual class matching, prop handling, or complex PostCSS setups, ensuring that your jade-garden definitions directly translate into optimized CSS. The plugin is required for class-utils like prefixes and traits, as it generates the class names that Tailwind needs to scan and process to create the final CSS bundle.

By handling this pre-computation, unplugin-jade-garden ensures your design system is both fully customizable and portable.

How it Works

The unplugin-jade-garden process is designed to be a seamless part of your build pipeline. Here is a step-by-step breakdown of how it converts your jade-garden configurations into a final CSS file:

  1. Configuration Intake: The plugin receives cva and sva configurations.
  2. Parses Definitions: For each configuration, it intelligently parses the class names defined in the base, variants, compoundVariants, slots, and compoundSlots properties.
  3. Generates CSS: It then generates corresponding CSS rules for each component and slot, using Tailwind's @apply directives.
  4. Writes to File: This generated CSS is written to a directory where you specify the entry file (e.g., styles/app.css). The plugin will create this file if it does not already exist.
  5. Tailwind Processing: When your main Tailwind CSS file is processed by PostCSS, the @apply directives generated by the plugin are expanded into plain CSS, ready for production.

Class Name Anatomy

It is a requirement that you include a name property for your cva and sva functions. This is responsible for generating class selectors for your components.

A generated class name will have the following anatomy:

"prefix:componentName--componentSlot__variantName--variantType"

Example Generated Output

For a component with a cva configuration, the plugin might generate a CSS file similar to this:

/* src/styles/cva/button.css (output by the plugin) */

.button {
  @apply inline-flex items-center justify-center rounded-md font-medium;
}

.button.button__variant--primary {
  @apply bg-blue-600 text-white hover:bg-blue-700;
}

.button.button__variant--secondary {
  @apply bg-gray-200 text-gray-800 hover:bg-gray-300;
}

For a component with an sva configuration, it generates classes for each slot:

/* src/styles/sva/card.css (output by the plugin) */

.card--root {
  @apply relative rounded-lg shadow-md;
}

.card--header {
  @apply p-4 border-b;
}

.card--body {
  @apply p-4;
}

/* ... and their variants, e.g., */
.card--root.card--root__flat--true {
  @apply shadow-none border border-gray-200;
}

Configuration

The plugin accepts an Options object to customize its behavior:

type Options = {
  /**
   * Will empty the output directory on every build.
   *
   * @default false
   */
  clean?: boolean;

  /**
   * An object containing arrays of `cva` and `sva` components.
   *
   * If `createOptions.useStylesheet` is set to `true`, **css** files will be generated.
   *
   * Otherwise, components will generate based on the `configOutput`.
   *
   * Object keys will generate directories in root of `outDir`.
   *
   * @default {}
   *
   * @example
   * ```ts
   * {
   *   components: {
   *     components: [accordion, alert, card],
   *     cva: [headingCVA, iconCVA, progressCVA],
   *     sva: [menuSVA, popoverSVA, skeletonSVA],
   *     ui: [button, image, input]
   *   },
   *   // Generates components if `createOptions.useStylesheet` is set to false.
   *   // Defaults to TypeScript.
   *   configOutput: "js",
   *   createOptions: {
   *     // Generates CSS files if set to `true`, otherwise output components.
   *     // Defaults to `false`.
   *     useStylesheet: true
   *   },
   *   entry: "./styles/main.css"
   * }
   * ```
   */
  components?: Record<string, (CVA | SVA)[]>;

  /**
   * The file format for the generated configs, if `createOptions.useStylesheet` is set to `false`.
   *
   * @default "ts"
   */
  configOutput?: "js" | "ts";

  /**
   * The options used to modify your class names for `createCVA` and `createSVA`.
   *
   * Use with `unplugin-jade-garden` to ensure consistent output of your CSS.
   *
   * @default {}
   *
   * @example
   * ```ts
   * import type { CreateOptions } from "jade-garden";
   * import { cn, createCVA, createSVA } from "jade-garden";
   *
   * export const createOptions: CreateOptions = {
   *   mergeFn: cn,
   *   prefix: "jg",
   *   useStylesheet: true
   * };
   *
   * export const cva = createCVA(createOptions);
   * export const sva = createSVA(createOptions);
   * ```
   */
  createOptions?: {
    /**
    * The function used to merge the classes.
    */
    mergeFn?: MergeFn;
    /**
    * The prefix for the class name.
    */
    prefix?: string;
    /**
    * Determines if the component returns classes for a stylesheet or not.
    * If `true` the class name generated is a combination of `base` and `variant` keys.
    * If `false`, defaults to the standard class merging functionality.
    *
    * In the plugin, this determines if you are outputting CSS or style configurations.
    */
    useStylesheet?: boolean;
  };

  /**
   * The main TailwindCSS file (relative to **project root**) where the generated CSS files will output.
   *
   * It is **recommended** that the main TailwindCSS file live in a dedicated directory
   * (e.g., `assets`, `css`, `styles`, etc.).
   *
   * @default process.cwd()
   *
   * @example
   * ```ts
   * {
   *   entry: "./styles/main.css"
   * }
   * ```
   */
  entry?: string;

  /**
   * **FOR LIBRARY AUTHORS**
   *
   * Add global JSDoc and CSS comments to the top of your main "index" file.
   *
   * @default {}
   */
  metaConfig?: {
    /**
    * Adds a `description` tag.
    *
    * @see https://jsdoc3.vercel.app/tags/description
    */
    description?: string;

    /**
    * Adds a `license` tag.
    *
    * @see https://jsdoc3.vercel.app/tags/license
    */
    license?: string;

    /**
    * Adds a `name` tag.
    *
    * `name` should be the same as `name` in "**package.json**".
    *
    * @see https://jsdoc3.vercel.app/tags/name
    */
    name?: string;

    /**
    * Adds a `see` tag.
    *
    * @see https://jsdoc3.vercel.app/tags/see
    */
    see?: string;

    /**
    * Adds a `version` tag.
    *
    * `version` should be the same as `version` in "**package.json**".
    *
    * @see https://jsdoc3.vercel.app/tags/version
    */
    version?: string;
  };


  /**
   * Specify the output directory (relative to **`entry`**).
   *
   * @default "jade-garden"
   *
   * @example
   * ```ts
   * {
   *   outDir: "themes/my-theme"
   * }
   * ```
   */
  outDir?: string;

  /**
   * Silence warnings that occur before an operation cancels or skips.
   *
   * @default false
   */
  silent?: boolean;
};