'How to write a Tailwind class plugin that takes a palette colour as an optional parameter

I have this style:

box-shadow: 0 0 0 100px white inset;

But I don't want white hard-coded in - I want to make a Tailwind plugin in my tailwind.config.js called bg-shadow that accepts the colour as an optional third argument, like so:

<input class="bg-shadow" /> <!-- defaults to white -->
<input class="bg-shadow-primary-500" />  <!-- specify the colour -->

Having read through the Tailwind docs on plugins, I am none the wiser as to how to achieve this!



Solution 1:[1]

You could do this with a pretty simple plugin using matchUtilities and flattenColorPalette.

In tailwind.config.js:

const plugin = require("tailwindcss/plugin");
const { default: flattenColorPalette } = require("tailwindcss/lib/util/flattenColorPalette");

module.exports = {
  ...
  plugins: [
    plugin(({ matchUtilities, theme }) => {
      matchUtilities(
        {
          "bg-shadow": (value) => ({
            boxShadow: `0 0 0 100px ${value} inset`
          })
        },
        { 
          values: flattenColorPalette(theme("colors")), 
          type: "color" 
        }
      );
    })
  ]
};

Solution 2:[2]

After studying this plugin (credit where credit's due) I have this now working as follows:

// tailwind.bg-shadow.plugin.js
const plugin = require("tailwindcss/plugin");

const formatShadow = (color) => `0 0 0 100px ${color} inset`;

module.exports = plugin.withOptions(
    ({className = "bg-shadow"} = {}) => {
        return ({e, addUtilities, theme, variants}) => {
            const colors = theme("colors");

            const caretColors = Object.keys(colors).reduce((acc, key) => {
                if (typeof colors[key] === "string") {
                    return {
                        ...acc,
                        [`.${className}-${e(key)}`]: {
                            "box-shadow": formatShadow(colors[key])
                        },
                    };
                }

                const colorShades = Object.keys(colors[key]);

                return {
                    ...acc,
                    ...colorShades.reduce(
                        (a, shade) => ({
                            ...a,
                            [`.${className}-${e(key)}-${shade}`]: {
                                "box-shadow": formatShadow(colors[key][shade])
                            },
                        }),
                        {}
                    ),
                };
            }, {});

            addUtilities(caretColors, variants("bg-shadowColor"));
        };
    },
    () => ({
        variants: {caretColor: ["dark", "active"]},
    })
);

And usage in tailwind.config.js:

module.exports = {
    ...
    plugins: [
        require('./tailwind.bg-shadow.plugin')
    ],
}

Solution 3:[3]

There are a few ways for you to extend box shadow.

Easier without a plugin, just configuration

Demo: https://play.tailwindcss.com/2mrdK51h8U?file=config

boxShadow property exists for you to extend.

module.exports = {
  theme: {
    extend: {
      boxShadow: '0 0 0 100px white inset',
      colors: {
        'primary-500': '5px 5px 15px 5px rgba(0,0,0,0.42)',
      }
      // Or do it in one shot
      // boxShadow: {
      //   DEFAULT: '0 0 0 100px white inset;',
      //   'primary-500': '5px 5px 15px 5px rgba(0,0,0,0.42)',
      // },
    },
  },
  plugins: [],
}

You can either extend boxShadow and colors separately or do it in one shot as shown above.

The downside is that, you need to use shadow-* prefix.
So let's check out the next option, using a plugin.

Using a plugin

Demo: https://play.tailwindcss.com/hJaWBntnq0?file=config

You can create a plugin that lets you or users to provide shadow colors.

matchUtilities lets you provide dynamic values (similar to addUtilities) using custom colors.

The second arg for theme(themeName, defaultThemeValue) is a default configuration you can provide in case users didn't provided their own.

const plugin = require('tailwindcss/plugin')

// This is in-case a user didn't extend `bgShadowColors`.
const defaultBgShadowColors = {
  DEFAULT: '0 0 0 100px red inset',
  'primary-100': '5px 5px 15px 5px rgba(0,0,0,0.12)',
  'primary-200': '5px 5px 15px 5px rgba(0,0,0,0.20)',
  'primary-300': '5px 5px 15px 5px rgba(0,0,0,0.22)',
  'primary-400': '5px 5px 15px 5px rgba(0,0,0,0.32)',
  'primary-500': '5px 5px 15px 5px rgba(0,0,0,0.42)',
  'primary-600': '5px 5px 15px 5px rgba(0,0,0,0.62)',
  'primary-700': '5px 5px 15px 5px rgba(0,0,0,0.82)',
}
const bgShadowPlugin = plugin(function ({ matchUtilities, theme }) {
  // Refer to https://tailwindcss.com/docs/plugins#dynamic-utilities
  matchUtilities(
    {
      'bg-shadow': (value) => ({
        'box-shadow': value,
      }),
    },
    // If user hasn't extended `bgShadowColors`, then use `defaultBgShadowColors`
    { values: theme('bgShadowColors', defaultBgShadowColors) }
  )
})

module.exports = {
  theme: {
    extend: {
      bgShadowColors: {
        DEFAULT: '0 0 0 100px red inset',
        'primary-100': '5px 5px 15px 5px rgba(0,0,0,0.12)',
        'primary-200': '5px 5px 15px 5px rgba(0,0,0,0.20)',
        'primary-300': '5px 5px 15px 5px rgba(0,0,0,0.22)',
        'primary-400': '5px 5px 15px 5px rgba(0,0,0,0.32)',
        'primary-500': '5px 5px 15px 5px rgba(0,0,0,0.42)',
        'primary-600': '5px 5px 15px 5px rgba(0,0,0,0.62)',
        'primary-700': '5px 5px 15px 5px rgba(0,0,0,0.82)',
      },
    },
  },
  plugins: [bgShadowPlugin],
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Chris Koelle
Solution 2 joshcomley
Solution 3 dance2die