12. Uso de iconos con Vue3 (Heroicons)

 

Uso de Iconos con Vue3 (Heroicons)

1. Introducción a Heroicons

1.1. ¿Qué son los Heroicons?

Heroicons es una librería de iconos gratuita creada por el equipo de Tailwind CSS.

Características principales:

  • ✅ Diseño profesional - Iconos modernos y limpios

  • ✅ Dos estilos - Outline (contorno) y Solid (relleno)

  • ✅ Integración nativa con Vue.js y React

  • ✅ Optimizados para Tailwind CSS

  • ✅ MIT License - Uso gratuito

1.2. ¿Por qué usar Iconos?

  • 🎯 Mejoran la UX - Comunicación visual más clara

  • 🎨 Apariencia profesional - UI más atractiva

  • 📱 Mejor usabilidad - Acciones identificables rápidamente

  • 🌍 Internacionalización - Menos dependencia del texto


2. Instalación de Heroicons

2.1. Instalar el Paquete

bash

npm install @heroicons/vue@2 --save-dev

Nota: La versión @2 es la última disponible.

2.2. Verificar Instalación

json

// package.json

"devDependencies": {

    "@heroicons/vue": "^2.0.0"

}

2.3. Estilos de Iconos

Heroicons ofrece dos estilos:

javascript

// Outline - Contorno (más ligero)

import { EyeIcon } from '@heroicons/vue/24/outline'


// Solid - Relleno (más llamativo)

import { EyeIcon } from '@heroicons/vue/24/solid'


// Mini - Tamaño 20px (para espacios pequeños)

import { EyeIcon } from '@heroicons/vue/20/solid'


3. Uso Básico de Iconos

3.1. Importar y Usar un Icono

En un componente Vue:

vue

<script setup>

import { EyeIcon } from '@heroicons/vue/24/solid'

</script>


<template>

    <div>

        <EyeIcon class="w-6 h-6 text-blue-500" />

        <span>Ver Chatbot</span>

    </div>

</template>

3.2. Tamaños Comunes

vue

<!-- Tamaño pequeño -->

<EyeIcon class="w-4 h-4" />


<!-- Tamaño mediano -->

<EyeIcon class="w-5 h-5" />


<!-- Tamaño grande -->

<EyeIcon class="w-6 h-6" />


<!-- Tamaño personalizado -->

<EyeIcon class="w-8 h-8" />

3.3. Colores con Tailwind

vue

<!-- Color primario -->

<EyeIcon class="w-5 h-5 text-blue-600" />


<!-- Color con hover -->

<EyeIcon class="w-5 h-5 text-gray-500 hover:text-blue-600" />


<!-- Color en modo oscuro -->

<EyeIcon class="w-5 h-5 text-gray-500 dark:text-gray-400" />


4. Ejemplo: ChatbotListItem con Iconos

4.1. Componente Actualizado

resources/js/Components/Chatbots/ChatbotListItem.vue:

vue

<script setup>

import { EyeIcon, PencilIcon, TrashIcon } from '@heroicons/vue/24/solid'

import { PrimaryButton, SecondaryButton, DangerButton } from '@/Components/Buttons'


const emit = defineEmits(['delete'])


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: {{ dayjs(chatbot.created_at).fromNow() }}

            </div>

        </div>


        <div class="flex space-x-2 mt-4 md:mt-0">

            <!-- Ver con icono -->

            <PrimaryButton 

                :href="route('chatbots.show', chatbot.id)"

                aria-label="Ver detalles del chatbot"

                class="gap-2"

            >

                <EyeIcon class="w-4 h-4" />

                <span>Ver</span>

            </PrimaryButton>

            

            <!-- Editar con icono -->

            <SecondaryButton 

                :href="route('chatbots.edit', chatbot.id)"

                aria-label="Editar chatbot"

                class="gap-2"

            >

                <PencilIcon class="w-4 h-4" />

                <span>Editar</span>

            </SecondaryButton>

            

            <!-- Eliminar con icono -->

            <DangerButton 

                aria-label="Eliminar chatbot"

                class="gap-2"

                @click="$emit('delete', chatbot.id)"

            >

                <TrashIcon class="w-4 h-4" />

                <span>Eliminar</span>

            </DangerButton>

        </div>

    </div>

</template>


5. Configuración de Espaciado

5.1. Gap en el Componente Base

Modificar BaseButton.vue:

vue

<script setup>

import { computed } from 'vue'

import { twMerge } from 'tailwind-merge'

import { Link } from '@inertiajs/vue3'


const props = defineProps({

    ariaLabel: {

        type: String,

        required: true

    },

    defaultClasses: {

        type: String,

        default: ''

    },

    class: {

        type: String,

        default: ''

    },

    href: {

        type: String,

        default: ''

    }

})


// Combinar clases con gap por defecto

const mergedClasses = computed(() => {

    return twMerge(

        'inline-flex items-center gap-2', // 👈 Gap por defecto

        props.defaultClasses,

        props.class

    )

})

</script>


<template>

    <Link

        v-if="href"

        :href="href"

        :class="mergedClasses"

        :aria-label="ariaLabel"

    >

        <slot />

    </Link>

    <button

        v-else

        :class="mergedClasses"

        :aria-label="ariaLabel"

    >

        <slot />

    </button>

</template>

5.2. Prioridad en tailwind-merge

Orden de las clases en twMerge:

javascript

// ✅ Las clases a la derecha tienen mayor prioridad

twMerge(

    'gap-2',        // Baja prioridad

    'gap-4'         // Alta prioridad - Sobrescribe

)

// Resultado: 'gap-4'


// ✅ Las clases más específicas deben ir a la derecha

twMerge(

    'p-4',          // Baja prioridad

    'p-6'           // Alta prioridad

)

// Resultado: 'p-6'


// ❌ Las clases a la izquierda pueden ser sobrescritas

twMerge(

    'text-red-500', // Se sobrescribe

    'text-blue-500' // Prevalece

)

// Resultado: 'text-blue-500'

5.3. Uso con Diferentes Gaps

vue

<!-- Gap por defecto (2) -->

<PrimaryButton>Ver</PrimaryButton>


<!-- Gap más amplio -->

<PrimaryButton class="gap-4">

    <EyeIcon class="w-4 h-4" />

    <span>Ver</span>

</PrimaryButton>


<!-- Sin gap -->

<PrimaryButton class="gap-0">

    <EyeIcon class="w-4 h-4" />

</PrimaryButton>


6. Ejemplos de Iconos por Categoría

6.1. Iconos de Acción

vue

<script setup>

import { 

    PlusIcon,          // Crear

    PencilIcon,        // Editar

    EyeIcon,           // Ver

    TrashIcon,         // Eliminar

    CheckIcon,         // Aceptar/Confirmar

    XMarkIcon,         // Cancelar/Cerrar

    ArrowPathIcon,     // Recargar

    ArrowLeftIcon,     // Volver

    ArrowRightIcon,    // Siguiente

} from '@heroicons/vue/24/solid'

</script>

6.2. Iconos de Navegación

vue

<script setup>

import { 

    HomeIcon,          // Inicio

    Cog8ToothIcon,     // Configuración

    UserIcon,          // Usuario

    UsersIcon,         // Usuarios

    EnvelopeIcon,      // Mensajes

    BellIcon,          // Notificaciones

    MagnifyingGlassIcon, // Búsqueda

} from '@heroicons/vue/24/solid'

</script>

6.3. Iconos de Estado

vue

<script setup>

import { 

    CheckCircleIcon,   // Éxito

    ExclamationCircleIcon, // Advertencia

    XCircleIcon,       // Error

    InformationCircleIcon, // Información

    ClockIcon,         // Tiempo/Pendiente

    StarIcon,          // Favorito

} from '@heroicons/vue/24/solid'

</script>


7. Componente de Icono Personalizado

7.1. IconWrapper para Reutilización

Crear resources/js/Components/IconWrapper.vue:

vue

<script setup>

import { computed } from 'vue'

import { twMerge } from 'tailwind-merge'


const props = defineProps({

    icon: {

        type: Object,

        required: true

    },

    size: {

        type: String,

        default: '5'

    },

    color: {

        type: String,

        default: 'text-gray-500'

    },

    class: {

        type: String,

        default: ''

    }

})


const sizeClasses = {

    '3': 'w-3 h-3',

    '4': 'w-4 h-4',

    '5': 'w-5 h-5',

    '6': 'w-6 h-6',

    '8': 'w-8 h-8',

    '10': 'w-10 h-10',

    '12': 'w-12 h-12',

}


const iconClasses = computed(() => {

    return twMerge(

        sizeClasses[props.size] || 'w-5 h-5',

        props.color,

        props.class

    )

})

</script>


<template>

    <component 

        :is="icon" 

        :class="iconClasses"

        aria-hidden="true"

    />

</template>

7.2. Uso del IconWrapper

vue

<script setup>

import IconWrapper from '@/Components/IconWrapper.vue'

import { EyeIcon, PencilIcon, TrashIcon } from '@heroicons/vue/24/solid'

</script>


<template>

    <div class="flex gap-2">

        <!-- Icono con tamaño personalizado -->

        <IconWrapper :icon="EyeIcon" size="4" />

        

        <!-- Icono con color personalizado -->

        <IconWrapper 

            :icon="PencilIcon" 

            size="5" 

            color="text-blue-600"

        />

        

        <!-- Icono con clases adicionales -->

        <IconWrapper 

            :icon="TrashIcon" 

            size="6" 

            class="text-red-500 hover:text-red-700 transition-colors"

        />

    </div>

</template>


8. Botones con Solo Icono

8.1. IconButton Component

Crear resources/js/Components/IconButton.vue:

vue

<script setup>

import { computed } from 'vue'

import BaseButton from './BaseButton.vue'

import { primaryClasses, secondaryClasses, dangerClasses } from '@/utils/buttonClasses'

import { twMerge } from 'tailwind-merge'


const props = defineProps({

    icon: {

        type: Object,

        required: true

    },

    variant: {

        type: String,

        default: 'primary'

    },

    size: {

        type: String,

        default: 'md'

    },

    ariaLabel: {

        type: String,

        required: true

    },

    href: {

        type: String,

        default: ''

    },

    class: {

        type: String,

        default: ''

    }

})


const variants = {

    primary: primaryClasses,

    secondary: secondaryClasses,

    danger: dangerClasses,

}


const sizeClasses = {

    sm: 'p-1.5',

    md: 'p-2',

    lg: 'p-2.5',

}


const iconSizes = {

    sm: 'w-3.5 h-3.5',

    md: 'w-4 h-4',

    lg: 'w-5 h-5',

}


const defaultClasses = computed(() => {

    return twMerge(

        variants[props.variant],

        sizeClasses[props.size],

        props.class

    )

})

</script>


<template>

    <BaseButton 

        :default-classes="defaultClasses"

        :aria-label="ariaLabel"

        :href="href"

    >

        <component 

            :is="icon" 

            :class="iconSizes[size]"

            aria-hidden="true"

        />

    </BaseButton>

</template>


<!-- Uso -->

<IconButton 

    :icon="EyeIcon" 

    variant="primary"

    size="md"

    aria-label="Ver chatbot"

    :href="route('chatbots.show', chatbot.id)"

/>


9. Lista de Iconos Más Usados

9.1. Iconos de Acción

Icono

Importación

Uso

👁️

EyeIcon

Ver/Visualizar

✏️

PencilIcon

Editar

🗑️

TrashIcon

Eliminar

PlusIcon

Crear/Añadir

CheckIcon

Confirmar

XMarkIcon

Cancelar/Cerrar

🔄

ArrowPathIcon

Recargar/Actualizar

⬅️

ArrowLeftIcon

Volver

➡️

ArrowRightIcon

Siguiente

9.2. Iconos de Navegación

Icono

Importación

Uso

🏠

HomeIcon

Inicio/Dashboard

⚙️

Cog8ToothIcon

Configuración

👤

UserIcon

Perfil/Usuario

👥

UsersIcon

Usuarios/Equipos

✉️

EnvelopeIcon

Mensajes/Email

🔔

BellIcon

Notificaciones

🔍

MagnifyingGlassIcon

Búsqueda

9.3. Iconos de Estado

Icono

Importación

Uso

CheckCircleIcon

Éxito/Confirmado

⚠️

ExclamationCircleIcon

Advertencia

XCircleIcon

Error/Rechazado

ℹ️

InformationCircleIcon

Información

StarIcon

Favorito/Destacado


10. Buenas Prácticas

10.1. Accesibilidad

vue

<!-- ✅ Bueno: aria-hidden para iconos decorativos -->

<EyeIcon class="w-5 h-5" aria-hidden="true" />


<!-- ✅ Bueno: aria-label cuando no hay texto -->

<button aria-label="Ver detalles">

    <EyeIcon class="w-5 h-5" aria-hidden="true" />

</button>


<!-- ✅ Bueno: texto con icono -->

<button>

    <EyeIcon class="w-5 h-5" aria-hidden="true" />

    <span>Ver</span>

</button>

10.2. Tamaños Consistentes

javascript

// ✅ Usar tamaños consistentes

const sizes = {

    sm: 'w-4 h-4',   // Iconos pequeños

    md: 'w-5 h-5',   // Iconos medianos

    lg: 'w-6 h-6',   // Iconos grandes

    xl: 'w-8 h-8',   // Iconos extra grandes

}

10.3. Rendimiento

javascript

// ✅ Mejor: Importar solo lo que usas

import { EyeIcon, PencilIcon } from '@heroicons/vue/24/solid'


// ❌ Peor: Importar todo

import * as Icons from '@heroicons/vue/24/solid'


11. Resumen

11.1. Instalación

bash

npm install @heroicons/vue@2 --save-dev

11.2. Uso Básico

vue

<script setup>

import { EyeIcon } from '@heroicons/vue/24/solid'

</script>


<template>

    <EyeIcon class="w-5 h-5 text-blue-600" />

</template>

11.3. Componentes Creados

Componente

Propósito

IconWrapper

Wrapper reutilizable para iconos

IconButton

Botón con solo icono

11.4. Beneficios

  • ✅ Interfaz más visual y profesional

  • ✅ Acciones identificables rápidamente

  • ✅ Menos dependencia del texto

  • ✅ Biblioteca extensa de iconos

  • ✅ Integración perfecta con Tailwind

¡Los iconos mejoran significativamente la experiencia de usuario! 


Comentarios

Entradas más populares de este blog

1. Introducción al curso de Chatbots personalizados con Laravel & OpenAI

2. Prerequisitos para el Curso de Chatbots con Laravel y OpenAI

4. Herramientas de Calidad