Todos los artículos

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.

Escribo estos artículos mientras diseño y programo interfaces para clientes. Si quieres algo así en tu proyecto, mira mis servicios de diseño y desarrollo frontend o cómo rehíce la web de la Clínica de Urgencias Bucaramanga.