10. Qué es y cómo utiliza Tailwind Merge
Tailwind Merge - Combinación Inteligente de Clases CSS
1. Introducción a Tailwind Merge
1.1. ¿Qué es Tailwind Merge?
Tailwind Merge es una utilidad que permite combinar cadenas de clases de Tailwind CSS y resolver conflictos automáticamente.
Problema que resuelve:
vue
<!-- Clases base del componente -->
<button class="bg-blue-500 text-white px-4 py-2 rounded">
<!-- Queremos sobrescribir -->
<button class="bg-blue-500 text-white px-4 py-2 rounded bg-red-500">
<!-- ❌ Conflicto: ¿azul o rojo? -->
Solución con Tailwind Merge:
vue
<button :class="twMerge('bg-blue-500 text-white px-4 py-2 rounded', 'bg-red-500')">
<!-- ✅ Resultado: bg-red-500 (prevalece la última) -->
1.2. Características Principales
✅ Resuelve conflictos automáticamente
✅ Maneja variantes (hover:, dark:, etc.)
✅ Ligero - Solo 3KB
✅ TypeScript - Soporte nativo
2. Instalación
2.1. Instalar Dependencia
bash
npm install --save-dev tailwind-merge
2.2. Verificar Instalación
json
// package.json
"devDependencies": {
"tailwind-merge": "^2.0.0"
}
3. Crear Componente de Botón Mejorado
3.1. Componente PrimaryButton Mejorado
Modificar resources/js/Components/PrimaryButton.vue:
vue
<script setup>
import { computed } from 'vue'
import { Link } from '@inertiajs/vue3'
import { twMerge } from 'tailwind-merge'
// Props del componente
const props = defineProps({
href: {
type: String,
default: ''
},
class: {
type: String,
default: ''
},
ariaLabel: {
type: String,
required: true
}
})
// Clases base del componente
const defaultClasses = [
'inline-flex',
'items-center',
'px-4',
'py-2',
'bg-blue-600',
'border',
'border-transparent',
'rounded-md',
'font-semibold',
'text-xs',
'text-white',
'uppercase',
'tracking-widest',
'hover:bg-blue-700',
'focus:bg-blue-700',
'active:bg-blue-900',
'focus:outline-none',
'focus:ring-2',
'focus:ring-blue-500',
'focus:ring-offset-2',
'transition',
'ease-in-out',
'duration-150',
'dark:bg-blue-600',
'dark:hover:bg-blue-700',
'dark:focus:bg-blue-700',
'dark:active:bg-blue-900',
'dark:focus:ring-blue-400',
'dark:focus:ring-offset-gray-800'
].join(' ')
// Clases finales combinadas
const classes = computed(() => {
return twMerge(defaultClasses, props.class)
})
</script>
<template>
<!-- Si hay href, renderizar Link -->
<Link
v-if="href"
:href="href"
:class="classes"
:aria-label="ariaLabel"
>
<slot />
</Link>
<!-- Si no hay href, renderizar botón -->
<button
v-else
:class="classes"
:aria-label="ariaLabel"
>
<slot />
</button>
</template>
4. Uso del Componente Mejorado
4.1. Como Botón Normal
vue
<PrimaryButton aria-label="Crear chatbot">
Crear Chatbot
</PrimaryButton>
4.2. Como Link de Inertia
vue
<PrimaryButton
href="/chatbots/1"
aria-label="Ver chatbot"
>
Ver Chatbot
</PrimaryButton>
4.3. Sobrescribiendo Clases
vue
<!-- Texto en rojo -->
<PrimaryButton
href="/chatbots/1"
class="text-red-500 hover:text-red-700"
aria-label="Ver chatbot"
>
Ver Chatbot
</PrimaryButton>
<!-- Fondo verde -->
<PrimaryButton
href="/chatbots/create"
class="bg-green-600 hover:bg-green-700"
aria-label="Crear chatbot"
>
Crear Chatbot
</PrimaryButton>
5. Funcionalidad de Tailwind Merge
5.1. Resolución de Conflictos
javascript
import { twMerge } from 'tailwind-merge'
// Ejemplos de resolución
twMerge('px-2 py-1', 'px-4') // 'py-1 px-4'
twMerge('bg-red-500', 'bg-blue-500') // 'bg-blue-500'
twMerge('text-sm text-gray-500', 'text-lg') // 'text-lg text-gray-500'
5.2. Variantes y Modificadores
javascript
// Variantes funcionan correctamente
twMerge('hover:bg-red-500', 'hover:bg-blue-500')
// 'hover:bg-blue-500'
// Dark mode
twMerge('dark:bg-gray-800', 'dark:bg-gray-900')
// 'dark:bg-gray-900'
// Múltiples estados
twMerge('focus:ring-2 focus:ring-blue-500', 'focus:ring-red-500')
// 'focus:ring-2 focus:ring-red-500'
5.3. Clases sin Conflicto
javascript
// Se combinan
twMerge('p-4', 'm-2') // 'p-4 m-2'
twMerge('flex', 'items-center') // 'flex items-center'
twMerge('w-full', 'h-auto') // 'w-full h-auto'
6. Mejoras Adicionales
6.1. Extraer Lógica a Utilidad
Crear resources/js/utils/twMerge.js:
javascript
import { twMerge as merge } from 'tailwind-merge'
export const twMerge = (baseClasses, ...additionalClasses) => {
return merge(baseClasses, ...additionalClasses)
}
// Función para crear variantes de botones
export const buttonVariants = {
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800',
danger: 'bg-red-600 hover:bg-red-700 text-white',
success: 'bg-green-600 hover:bg-green-700 text-white',
}
6.2. Componente Genérico de Botón
vue
<script setup>
import { computed } from 'vue'
import { Link } from '@inertiajs/vue3'
import { twMerge, buttonVariants } from '@/utils/twMerge'
const props = defineProps({
variant: {
type: String,
default: 'primary',
validator: (value) => ['primary', 'secondary', 'danger', 'success'].includes(value)
},
href: {
type: String,
default: ''
},
class: {
type: String,
default: ''
},
ariaLabel: {
type: String,
required: true
}
})
const baseClasses = computed(() => {
return twMerge(
'px-4 py-2 rounded-md font-semibold transition duration-150 ease-in-out',
buttonVariants[props.variant],
props.class
)
})
</script>
<template>
<Link v-if="href" :href="href" :class="baseClasses" :aria-label="ariaLabel">
<slot />
</Link>
<button v-else :class="baseClasses" :aria-label="ariaLabel">
<slot />
</button>
</template>
6.3. Uso con Variantes
vue
<!-- Botón primario (default) -->
<Button aria-label="Guardar">Guardar</Button>
<!-- Botón secundario -->
<Button variant="secondary" aria-label="Cancelar">Cancelar</Button>
<!-- Botón de peligro -->
<Button variant="danger" aria-label="Eliminar">Eliminar</Button>
<!-- Botón de éxito -->
<Button variant="success" aria-label="Aceptar">Aceptar</Button>
<!-- Con href -->
<Button href="/chatbots" variant="primary" aria-label="Ver chatbots">
Ver Chatbots
</Button>
7. Ejemplos Prácticos
7.1. Componente ChatbotListItem Actualizado
vue
<script setup>
import dayjs, { timeAgo } from '@/utils/dayjs'
import { PrimaryButton, SecondaryButton, DangerButton } from '@/Components/Buttons'
defineProps({
chatbot: {
type: Object,
required: true
}
})
</script>
<template>
<div class="flex flex-col md:flex-row md:items-center md:justify-between py-4">
<div class="flex-1">
<div class="text-gray-800 dark:text-gray-200 font-medium">
{{ chatbot.name }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
Creado: {{ timeAgo(chatbot.created_at) }}
</div>
</div>
<div class="flex space-x-2 mt-4 md:mt-0">
<!-- Ahora los botones pueden ser links -->
<PrimaryButton
:href="route('chatbots.show', chatbot.id)"
aria-label="Ver chatbot"
>
Ver
</PrimaryButton>
<SecondaryButton
:href="route('chatbots.edit', chatbot.id)"
aria-label="Editar chatbot"
>
Editar
</SecondaryButton>
<DangerButton
aria-label="Eliminar chatbot"
@click="$emit('delete', chatbot.id)"
>
Eliminar
</DangerButton>
</div>
</div>
</template>
7.2. Botón con Icono
vue
<script setup>
import { twMerge } from '@/utils/twMerge'
const props = defineProps({
icon: {
type: Boolean,
default: false
},
// ... otros props
})
const classes = computed(() => {
return twMerge(
'inline-flex items-center gap-2',
props.icon && 'px-2 py-2',
!props.icon && 'px-4 py-2',
defaultClasses,
props.class
)
})
</script>
<template>
<button :class="classes">
<slot name="icon" />
<slot v-if="!icon" />
</button>
</template>
<!-- Uso -->
<Button icon aria-label="Ajustes">
<template #icon>
<svg class="w-5 h-5">...</svg>
</template>
</Button>
8. Configuración Avanzada
8.1. Configuración Personalizada
javascript
import { extendTailwindMerge } from 'tailwind-merge'
const customTwMerge = extendTailwindMerge({
extend: {
classGroups: {
'my-custom-class': ['custom-class-1', 'custom-class-2'],
},
conflictingClassGroups: {
'my-custom-class': ['another-class'],
},
}
})
8.2. Plugins de Tailwind
javascript
// Con plugins de Tailwind
import { twMerge } from 'tailwind-merge'
import { withTailwind } from 'tailwind-merge/with-tailwind'
const merge = withTailwind({
// Configuración con plugins
})
// O usar directamente
const merged = twMerge('px-2 py-1', 'px-4')
9. Buenas Prácticas
9.1. Organización de Clases
javascript
// ❌ Mal: Desorganizado
const classes = twMerge(
'p-4',
'text-center',
'bg-white',
'shadow-lg',
'rounded',
props.class
)
// ✅ Bien: Agrupado lógicamente
const classes = computed(() => {
return twMerge(
// Layout
'flex items-center justify-between',
// Espaciado
'p-4 space-x-4',
// Estilos
'bg-white shadow-lg rounded-lg',
// Estados
'hover:shadow-xl transition-shadow duration-300',
// Clases personalizadas
props.class
)
})
9.2. Reutilización de Estilos
javascript
// utils/tailwind.js
export const baseButtonClasses = `
inline-flex
items-center
justify-center
px-4
py-2
rounded-md
font-medium
transition-colors
duration-200
`
export const buttonSizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg'
}
10. Resumen
10.1. Beneficios de Tailwind Merge
✅ Resuelve conflictos automáticamente
✅ Mejora la reutilización de componentes
✅ Flexibilidad para sobrescribir estilos
✅ Mantiene la consistencia visual
10.2. Archivos Modificados
10.3. Instalación
bash
npm install --save-dev tailwind-merge
¡Ahora los botones pueden ser links y mantener estilos consistentes!
Comentarios
Publicar un comentario