Error Detection
Per the documentation on class groups configuration:
The library utilizes the concept of class groups, which is an array of Tailwind classes that modify the same CSS property. For example, here is the position class group.
const positionClassGroup = ['static', 'fixed', 'absolute', 'relative', 'sticky']
tailwind-merge handles conflicts between classes in a class group and only retains the last one passed to the merge function call.
This implies that your text-shadow-<size>
and text-shadow-<color>
classes would override each other, not to mention the text-<color>
class. Therefore, only the text-shadow-*
class gets rendered (based on testing, it was the text-shadow-red-500
class since it was last in the twMerge()
call).
In any case, your class group configuration is incorrect:
extend: {
classGroups: {
'text-shadow': [
'sm',
'DEFAULT',
'md',
'lg',
It actually registers the classes sm
, DEFAULT
, md
, lg
, etc., not text-shadow-sm
, text-shadow
, text-shadow-md
, text-shadow-lg
as might have been expected.
Instead, the initial key should represent the class group "ID", containing an array of objects where those keys can be a class name prefix with an array of values:
extend: {
classGroups: {
'text-shadow': [{ 'text-shadow': […] }]
Resolution
Further details from the same Class Groups documentation are provided:
Tailwind classes often share the beginning of the class name, allowing elements in a class group to be represented by an object with values following the same pattern as the class group (recursive shape). Within the object, each key combines with all elements in the corresponding array using a dash (-
) in between.
For instance, consider the overflow class group resulting in classes like overflow-auto
, overflow-hidden
, overflow-visible
, and overflow-scroll
.
const overflowClassGroup = [{ overflow: ['auto', 'hidden', 'visible', 'scroll'] }]
Examples from the default configuration also demonstrate this pattern for shared prefixes:
'font-size': [{ text: ['base', isTshirtSize, isArbitraryLength] }],
// …
'text-alignment': [{ text: ['left', 'center', 'right', 'justify', 'start', 'end'] }],
// …
'text-color': [{ text: [colors] }],
'font-weight': [
{
font: [
'thin',
'extralight',
// …
'font-family': [{ font: [isAny] }],
Adopting this approach in our own configuration entails defining the first key as a group ID, encapsulating our class name specifications and dividing into two distinct class groups:
extend: {
classGroups: {
'text-shadow-size': [
{
'text-shadow': [
'sm',
'',
'md',
'lg',
'xl',
'2xl',
'3xl',
'none',
],
},
],
'text-shadow-color': [
{
'text-shadow': [
...colorList,
'transparent',
'white',
'black',
],
},
],
},
},
Furthermore, replacing the 'DEFAULT'
entry with ''
is necessary. This is due to the fact that 'DEFAULT'
would translate to text-shadow-DEFAULT
, whereas the actual class name in Tailwind should be text-shadow
.
// tailwind.config.ts
const flattenColorPalette = (colors)=>Object.assign({}, ...Object.entries(colors !== null && colors !== void 0 ? colors : {}).flatMap(([color, values])=>typeof values == "object" ? Object.entries(flattenColorPalette(values)).map(([number, hex])=>({
[color + (number === "DEFAULT" ? "" : `-${number}`)]: hex
})) : [
{
[`${color}`]: values
}
]));
/**
* ### Decimal Alpha to HEX
* - Converts an RGB decimal alpha value to hexadecimal alpha format
* @param decimalAlpha
* @returns
*/
function decimalAlphaToHex(decimalAlpha) {
// Ensure the input falls within the valid range
if (decimalAlpha < 0 || decimalAlpha > 1)
throw new Error('Decimal alpha value must be between 0 and 1')
// Convert decimal alpha to a hexadecimal value
const alphaHex = Math.floor(decimalAlpha * 255)
.toString(16)
.toUpperCase()
// Guarantee the hexadecimal value consumes two digits (e.g., 0A instead of A)
if (alphaHex.length < 2) {
return '0' + alphaHex
} else {
return alphaHex
}
}
tailwind.config = {
theme: {
textShadow: {
sm: '0 0 0.125rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
DEFAULT: '0 0 0.25rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
md: '0 0 0.5rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
lg: '0 0 0.75rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
xl: '0 0 1rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
'2xl': '0 0 2rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
'3xl': '0 0 3rem var(--tw-text-shadow, hsl(0 0% 0% / 0.25))',
none: 'none',
},
},
plugins: [
tailwind.plugin(function ({ matchUtilities, theme }) {
const colors = {},
opacities = flattenColorPalette(
theme('opacity')
),
opacityEntries = Object.entries(opacities)
Object.entries(flattenColorPalette(theme('colors'))).forEach((color) => {
const [key, value] = color
...
Note that your text-shadow-<color>
utilities define --tw-text-shadow-color
CSS variables while your text-shadow-<size>
utilities use --tw-text-shadow
. Hence, no text shadow displays in the above preview, but you can inspect the elements to verify their attached class names.