Combining Tailwind with Color Schemes for Stylish Text and Text Shadow Effects

tl;dr I have a utility class in my tailwind.config.ts for customizing text shadows' dimensions and colors. However, when using Tailwind Merge, I face conflicts between text-shadow-{size/color} and text-{color}.

The Issue

In CSS, text shadows are often useful for enhancing text designs or creating contrast without drop shadows. My custom text-shadow utility in Tailwind Config works well unless applied on a component with Tailwind Merge, causing confusion due to conflicts.

The Fix

To address this problem, I aimed to use extendTailwindMerge. Although the documentation is detailed, it lacks specific examples beyond foo, bar, and baz, making it challenging to implement bespoke solutions.

Seeking Help

I'm seeking advice on modifying my tailwind.config.ts and custom twMerge() function to resolve this issue. Any insights or suggestions would be greatly appreciated. Thank you!

The Code

// Updated code goes here

My Attempt

// Additional tweaks in progress

Illustrative Example

Input

// Illustrative scenario 

Expected Output

<a href='https://andrilla.net' class='text-blue-500 text-shadow-lg text-shadow-red-500'>Website</a>

Actual Result

<a href='https://andrilla.net' class='text-shadow-lg'>Website</a>

Answer №1

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.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Monitoring the flow of data between Angular JS resources and the promise responses

In my application, there is a grid consisting of cells where users can drag and drop images. Whenever an image is dropped onto a cell, a $resource action call is triggered to update the app. My goal is to display a loader in each cell while the update cal ...

Prevent duplicate items in an array by utilizing the Map object to add elements

Looking for a way to update an array by adding new values while avoiding duplicates. I've implemented the usage of a Map object to keep track of existing values and tried filtering the new array accordingly. const [items, setItems] = useState([]); ...

React ES6 SystemJS encountered an unforeseen token error that couldn't be caught

Even though I have imported react and react-dom using the System.config setup below, I am still encountering the error mentioned here: Uncaught (in promise) Error: Unexpected token <(…) Here is the HTML structure: <!DOCTYPE html> <html l ...

Triggering AJAX call from several buttons within a single page in Django

Hey there! I'm currently working on implementing a voting feature using Ajax in my Django-based website. The issue I'm facing is that users can only vote on the first entry, but I want them to be able to vote on all entries. Can you assist me wit ...

Angular, delete any item from the scope that has a matching key value

One of the challenges I am facing is removing items from an array with the same key value of skillId when a button in the repeat is clicked. Here's the code snippet I have worked on: $scope.deleteSkill = function(skill) { for (var i=0; i<$ ...

Eliminating repeating entries in autocomplete results JSON

I am facing a challenge where I have integrated two feature classes, resulting in occasional duplication of results in the autosuggest feature. I am exploring options to detect and display alternatives instead of repeating the same result twice. Check out ...

Node.js encountered an SFTP error stating "Error: connect: An existing SFTP connection is already defined."

Working within my node.js application, I have implemented ssh2-sftp-client to upload an image every 5 seconds. The initial upload functions correctly, but upon repeating the process, I encounter an error message: node .\upload.js uploaded screenshot ...

Get rid of the folder from the URL using an <a> tag

I have both an English and French version of my website located at: *website.com/fr/index.php *website.com/index.php Currently, I have a direct link to switch between the two versions: -website.com/fr/index.php -website.com/index.php. However, I ...

Learn how to showcase a modal pop-up in AngularJS by utilizing the ng-if directive

Hello, I am new to working with AngularJS. My question is how can I display a modal pop-up box when the ng-if statement evaluates to false? Can you please provide guidance on how to solve this issue? Here is an example of the code snippet in HTML: <di ...

Divide Angular ngFor into separate divs

Here is an example of my current array: [a, b, c, d, e, f, g, h, i] I am aiming to iterate through it using ngFor and split it into groups of 3 elements. The desired output should look like this: <div class="wrapper"> <div class="main"> ...

Get the JS file by tapping the download button, and access the

In creating the web page, I utilize a modular approach. Leveraging node js and the node-static server are essential components of this process. One specific requirement I have is implementing file downloads from a computer to a device using a button trigg ...

Retrieve the value of a dynamically added or removed input field in JQuery using Javascript

Check out this informative article here I'm looking for a way to gather the values from all the text boxes and store them in an array within my JavaScript form. I attempted to enclose it in a form, but I'm struggling to retrieve the HTML ID beca ...

The active link for pagination in CodeIgniter is malfunctioning

Even though there might be similar posts on StackOverflow, my situation is unique. Hence, I have decided to ask this question with a specific title. Let me break down my issue into smaller parts: Part - 1: I have a regular view page where I can select a ...

The React Hook "useDispatch" is not permitted to be called at the top level of the code. It should only be used within a React function component or a custom React Hook function

I am currently working on building an authentication system using react hooks. However, I encountered an error when trying to declare and call a constant within a react component. Can anyone advise me on the correct place to declare a constant or function? ...

Tri-party class switch-up

I successfully implemented two radio buttons to toggle between alternative texts: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> ...

The argument in question has not been defined

Experimenting with AngularJS, I encountered an error message in the browser console when trying to display a particular example: Error: Argument 'mainController as mainCtrl' is not a function, got undefined at Error (native) at bb My pr ...

Creating beautiful user interfaces with Material UI and React

I'm currently exploring how to integrate Material UI into my React project. After successfully installing the module, I attempted to create a custom button component. Here is my Button.js file: import React from 'react'; import FlatButton ...

The unusual interactions between JavaScript and QML

Encountering strange behavior with JavaScript. I am currently working on a basic example application using QT-QML and JavaScript. Within this application, I have implemented HTTP Requests triggered by a button that sends the request through JavaScript. ...

Having trouble with jQuery toggle fade: Unable to make the div fade in/out when toggling

Is there a way to modify the functionality of my black button so that when clicked, the red div fades out while the blue div fades in? Right now, clicking the button switches between the two divs without any fading effect. Fiddle: http://jsfiddle.net/ddac ...

Storing props in JSX components using variables in React.jsLearn how to set props in JSX components and

If I have already created a component: class Co extends React.Component { render = () => { const name = this.props.name; return ( <p>Hello, my name is {name}</p> ) } } and stored it in a variable ...