De machacateclas a artesano del frontend: la guía para dominar los componentes reutilizables en Vue
Vue · 25 oct 2026 · Michael Quintero

Componentes reutilizables en Vue: menos copiar y pegar, más elegancia
Todos hemos caído en la trampa. Necesitas un botón parecido al que ya hiciste. ¿Qué haces? Ctrl + C, Ctrl + V. Unos días después, necesitas otro. Ctrl + C, Ctrl + V. De repente tienes diez botones idénticos y, cuando el diseñador pide un cambio de color, te das cuenta de que tienes que tocar diez archivos.
Copiar y pegar es enemigo de la escalabilidad. La solución son los componentes reutilizables. Hoy te enseño a crear componentes inteligentes y flexibles que te ahorrarán tiempo y mantendrán tu código limpio y profesional.
Los tres pilares de un componente reutilizable
Para que un componente sea realmente reutilizable, tiene que ser flexible. Y para eso necesitas dominar tres conceptos clave: props, slots y emits.
1. Props: la flexibilidad es la clave
Las props son como los «argumentos» que le pasas a un componente. Te permiten cambiar su comportamiento o su aspecto.
Ejemplo: un botón flexible
<!-- BaseButton.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps({
label: { type: String, required: true },
variant: {
type: String,
default: 'primary',
validator: (value) => ['primary', 'secondary', 'danger'].includes(value),
},
size: { type: String, default: 'md' },
type: { type: String, default: 'button' },
disabled: { type: Boolean, default: false },
})
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
}
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2',
lg: 'px-6 py-3 text-lg',
}
const classes = computed(() => [
'rounded-lg font-semibold transition-colors',
variants[props.variant],
sizes[props.size],
props.disabled && 'cursor-not-allowed opacity-50',
])
</script>
<template>
<button :type="type" :class="classes" :disabled="disabled">
{{ label }}
</button>
</template>
Uso:
<script setup>
import BaseButton from '@/components/BaseButton.vue'
const save = () => console.log('¡Guardado!')
</script>
<template>
<BaseButton label="Guardar" @click="save" />
<BaseButton label="Cancelar" variant="secondary" size="sm" />
<BaseButton label="Borrar la cuenta" variant="danger" size="lg" disabled />
</template>
2. Slots: para inyectar contenido
Los slots son huecos dentro de un componente donde el padre puede «inyectar» su propio contenido.
Ejemplo: una tarjeta genérica
<!-- BaseCard.vue -->
<template>
<article class="overflow-hidden rounded-xl bg-white shadow-md">
<!-- Slot con nombre: solo se pinta si el padre lo rellena -->
<header v-if="$slots.header" class="border-b px-6 py-4 font-bold">
<slot name="header" />
</header>
<!-- Slot por defecto, con contenido de reserva -->
<div class="px-6 py-4">
<slot>Todavía no hay nada que mostrar.</slot>
</div>
<footer v-if="$slots.footer" class="bg-gray-50 px-6 py-4">
<slot name="footer" />
</footer>
</article>
</template>
Uso:
<template>
<BaseCard>
<template #header>Plan Pro</template>
<p>Proyectos ilimitados, soporte prioritario y dominio propio.</p>
<template #footer>
<BaseButton label="Elegir plan" />
</template>
</BaseCard>
<!-- Sin contenido, la tarjeta muestra su texto de reserva -->
<BaseCard />
</template>
3. Emits: hablar con el padre
Los emits permiten que un componente hijo se comunique con su padre. Es una forma de decir: «¡Eh, aquí ha pasado algo!».
Ejemplo: un formulario que emite un evento
<!-- NewsletterForm.vue -->
<script setup>
import { ref } from 'vue'
const emit = defineEmits({
// Valida lo que se envía: el evento solo sale con un correo válido
subscribe: (email) => email.includes('@'),
})
const email = ref('')
const submit = () => {
emit('subscribe', email.value.trim())
email.value = ''
}
</script>
<template>
<form class="flex gap-2" @submit.prevent="submit">
<label for="newsletter-email" class="sr-only">Correo</label>
<input
id="newsletter-email"
v-model="email"
type="email"
required
placeholder="tu@correo.com"
class="flex-1 rounded-lg border px-4 py-2"
/>
<BaseButton type="submit" label="Suscribirme" />
</form>
</template>
Uso en el padre:
<script setup>
import { ref } from 'vue'
import NewsletterForm from '@/components/NewsletterForm.vue'
const message = ref('')
// El hijo solo avisa de que alguien se suscribió; el padre decide qué hacer
const handleSubscribe = (email) => {
message.value = `¡Gracias! Te escribiremos a ${email}.`
}
</script>
<template>
<NewsletterForm @subscribe="handleSubscribe" />
<p v-if="message" class="mt-2 text-green-700">{{ message }}</p>
</template>
Conclusión
Crear componentes reutilizables no es solo un buen hábito, es una mentalidad que te convierte de simple «picacódigo» en «arquitecto de interfaces». Empieza a ver tu código como un juego de piezas de Lego bien diseñadas y verás cómo se disparan tu velocidad de desarrollo y la calidad de tu código.