DEV Community

Cover image for Gestiona clases de Tailwind CSS sin conflictos
José Robinson
José Robinson

Posted on • Originally published at joserobinson.com

Gestiona clases de Tailwind CSS sin conflictos

Si alguna vez has lidiado con clases de Tailwind que se acumulan y generan conflictos, esta pequeña función que encontré en shadcn-ui es tu solución ideal. Para mí, se ha vuelto imprescindible en todos mis proyectos con Tailwind CSS.

Básicamente, te ayuda a gestionar las clases de manera eficiente, algo que es clave para mantener un código limpio y organizado. Esta función ofrece tres ventajas: corrige la duplicación de clases, los conflictos de estilos entre clases que modifican las mismas propiedades, y permite asignar clases de forma organizada usando condicionales, objetos y matrices. Aquí te explico cómo funciona y por qué es imprescindible.

Primero, te muestro la función con algo de documentación para facilitar su uso, aunque su simplicidad la hace autoexplicativa.

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

/**
 * Merge Tailwind CSS classes efficiently.
 *
 * @param inputs - Class values to merge.
 * @returns Merged class names.
 */
export function cn(...inputs: ClassValue[]): string {
  return twMerge(clsx(inputs));
}
Enter fullscreen mode Exit fullscreen mode

Aquí tienes un gist con la función.

Como usarla

Primero que nada, debes instalar las librerías requeridas. Por ejemplo, si usas pnpm, ejecutarías esto:

pnpm add tailwind-merge clsx
Enter fullscreen mode Exit fullscreen mode

Luego, el ejemplo más simple de cómo se usa sería este:

cn('bg-gray-500 px-2 py-1 text-black');

// Lo que nos daría este resultado:
// => bg-gray-500 px-2 py-1 text-black
Enter fullscreen mode Exit fullscreen mode

Bastante simple, ¿no?

Ahora bien, imagina que tienes clases que entran en conflicto, como por ejemplo, modificar el padding varias veces. Lo que hace la función es reconocer que la última clase entra en conflicto con las anteriores y las elimina del resultado final:

cn('px-2 py-1 p-3');

// Resultando en esto:
// => p-3
Enter fullscreen mode Exit fullscreen mode

Quizás esto no te haga mucho sentido porque podrías pensar que es fácil identificar el problema y corregirlo tú mismo. Pero, ¿qué pasa cuando tienes una estructura compleja de clases basada en condiciones, y dentro de esas condiciones hay clases que deben anular las anteriores? Mira este ejemplo de un componente Buttom en React:

const Button = ({
  primary,
  className,
}: {
  primary?: boolean;
  className?: string;
}): ReactNode => {
  return (
    <button
      className={cn(
        'h-10 w-24 bg-gray-500 text-white',
        primary && 'bg-amber-400 text-black',
        className,
      )}
    >
      {primary ? 'Primary' : 'Default'}
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

En este ejemplo, se está aplicando un color de fondo y de texto por defecto. Sin embargo, estas se sustituyen cuando se pasa la propiedad primary, como puedes ver en esta imagen:

Botones que muestran la diferencia de clases

¿Aún no le ves el sentido a la función? Chequea este otro ejemplo aquí abajo:

const Button = ({
  variant,
  external,
  size,
  disabled,
  className,
}: {
  variant?: 'default' | 'primary' | 'secondary' | 'danger' | 'success';
  size?: 'sm' | 'lg';
  external?: boolean | string;
  disabled?: boolean;
  className?: string;
}): ReactNode => {
  return (
    <button
      className={cn(
        'h-10 w-24',
        size ? `text-${size}` : 'text-base',
        {
          'bg-blue-600 text-white': variant === 'primary',
          'bg-red-600 text-white': variant === 'danger',
          'bg-green-600 text-white': variant === 'success',
          'bg-gray-200 text-gray-800': variant === 'secondary',
          'bg-gray-500 text-white': !variant || variant === 'default',
        },
        disabled && 'pointer-events-none cursor-not-allowed opacity-50',
        external &&
          (external === true
            ? 'after:content-["_↗"]'
            : `after:content-["${external}"]`),
        className,
      )}
    >
      Click me!
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

Aquí la situación se ve más complicada, ¿no? Y toma en cuenta que este es un simple botón. Imagina dónde quieras implementar algo más complejo.

Conclusión

La función simplemente emplea estas dos utilidades para ofrecer las ventajas que te mencioné al principio:

  • clsx proporciona una forma sencilla de combinar clases basadas en condiciones.
  • tailwind-merge combina eficientemente las clases, evitando conflictos de estilo y asegurando que las clases posteriores anulen correctamente a las anteriores.

En mi opinión, esta es una utilidad imprescindible para cualquier proyecto con Tailwind CSS, especialmente cuando necesitas implementar estilos complejos. Su simplicidad y su capacidad para evitar conflictos de estilos te ahorrarán tiempo y dolores de cabeza.

Un extra: Vale la pena mencionar que existe otra utilidad llamada tailwind-variants que permite definir variantes de estilos de manera declarativa y con tipado en TypeScript. Sin embargo, prefiero dejarlo fuera del artículo para mantener el enfoque en proporcionar una solución concreta a un problema específico. Pero te lo menciono por si quieres profundizar más en el tema.

Top comments (0)