W3docs

Elemento Template

Aprende cómo el elemento HTML <template> almacena marcado reutilizable inerte y cómo clonar su contenido con JavaScript para crear interfaces dinámicas de forma eficiente.

El elemento HTML <template> te permite declarar un fragmento de marcado en tu página que el navegador analiza pero no renderiza. El contenido queda disponible en el documento, inerte, hasta que tu JavaScript lo clona e inserta en el DOM activo. Esto convierte a <template> en la forma idiomática de definir un "plano" para interfaces de usuario repetidas — filas de listas, tarjetas, diálogos modales — sin construir nodos del DOM a mano ni pegar cadenas HTML en innerHTML.

Este capítulo cubre qué hace especial al contenido de una plantilla, cómo clonarla e insertarla correctamente, cómo conectar eventos en los nodos clonados y los errores comunes que cometen los usuarios por primera vez. Las plantillas son también un bloque de construcción de los Web Components, donde se combinan de forma natural con los elementos personalizados y el Shadow DOM.

Comprendiendo el Elemento Template

El elemento <template> actúa como un contenedor para almacenar HTML que no se renderiza de inmediato. Lo que hace que su contenido sea inerte es la idea central:

  • No se muestra — el marcado nunca se pinta, independientemente del CSS.
  • Sus recursos no se cargan<img>, <video> y <script> dentro de una plantilla no se cargan ni ejecutan hasta que el contenido es clonado en el documento activo.
  • Los scripts que contiene no se ejecutan, y los IDs dentro de ella no colisionan con el resto de la página hasta que se activa.

Esto es fundamentalmente diferente a ocultar un elemento con display: none. Un elemento con display: none sigue siendo parte del DOM activo: sus imágenes se cargan, sus scripts se ejecutan, y document.getElementById encuentra los elementos dentro de él. El contenido de una plantilla vive en un árbol separado respaldado por un fragmento de documento, por lo que nada de eso ocurre hasta que lo activas clonándolo.

<body>
  <div>You won't see the template, as it's not activated using JS.</div>
  <div id="template-container">
    <template id="my-template">
      <h1>Hidden!</h1>
    </template>
  </div>
</body>

Clonando Plantillas para Contenido Dinámico

Una plantilla es solo un plano — no produce ninguna salida visible por sí sola. Para usarla, lees su propiedad content (un DocumentFragment), clonas ese fragmento, completas tus datos y añades el clon a la página. Como clonas para cada instancia, una sola plantilla puede producir tantas copias independientes como necesites, cada una con sus propios datos.

Hay dos formas de clonar:

  • template.content.cloneNode(true) — clona el fragmento en el lugar. El argumento true significa un clon profundo (incluye todos los descendientes); sin él solo copiarías el fragmento vacío.
  • document.importNode(template.content, true) — realiza el mismo clon profundo pero importa explícitamente los nodos al documento actual. Esto importa cuando la plantilla vive en un documento diferente (por ejemplo, contenido traído mediante un <iframe> o <link rel="import">). Para plantillas en el mismo documento los dos son equivalentes; importNode es la opción más segura por defecto.
Advertencia

Siempre clona el contenido de la plantilla antes de insertarlo. Si añades template.content directamente, mueves los nodos originales fuera de la plantilla hacia el DOM — la plantilla queda entonces vacía y el siguiente clon no produce nada. Clonar deja el plano intacto para reutilizarlo.

<head>
  <style>
    .card {
      border: 1px solid #ccc;
      border-radius: 5px;
      padding: 10px;
      margin: 10px;
      width: 200px;
    }
    .card h3 {
      margin: 0;
    }
  </style>
</head>
<body>
  <div id="template-container">
    <!-- Template element -->
    <template id="card-template">
      <div class="card">
        <h3 id="card-title">Title</h3>
        <p id="card-content">Content goes here...</p>
      </div>
    </template>
  </div>

  <div id="card-container">
    <!-- Cards will be inserted here -->
  </div>

  <script>
    // Data for multiple cards
    const cardData = [
      { title: 'Card 1', content: 'This is the first card.' },
      { title: 'Card 2', content: 'This is the second card.' },
      { title: 'Card 3', content: 'This is the third card.' }
    ];

    // Function to create and insert cards
    function createCards(data) {
      const template = document.getElementById('card-template');

      data.forEach(item => {
        const clone = document.importNode(template.content, true);

        // Customize the cloned content
        clone.querySelector('#card-title').textContent = item.title;
        clone.querySelector('#card-content').textContent = item.content;

        // Insert the cloned content into the DOM
        document.getElementById('card-container').appendChild(clone);
      });
    }

    // Create cards with the provided data
    createCards(cardData);
  </script>
</body>

Mejorando la Interactividad con JavaScript

La propiedad content de una plantilla devuelve un DocumentFragment que contiene el marcado inerte. Puedes consultarlo y leerlo (template.content.querySelector(...)) sin tocar la página activa, pero un patrón más limpio es clonar primero y luego conectar eventos al clon — de ese modo cada instancia obtiene sus propios listeners. Para una cobertura más profunda sobre cómo adjuntar manejadores, consulta Manejo de Eventos en el DOM.

El ejemplo siguiente define dos plantillas: un botón y una tarjeta de contenido. Al hacer clic en el botón se clona la plantilla de la tarjeta y se añade una copia nueva cada vez.

<body>
  <div id="template-container">
    <!-- Button Template -->
    <template id="button-template">
      <button id="show-content-btn">Add a content card</button>
    </template>

    <!-- Content Template -->
    <template id="content-template">
      <div class="content">
        <h2>Dynamic Content</h2>
        <p>This content is added dynamically when the button is clicked.</p>
      </div>
    </template>
  </div>

  <div id="button-container">
    <!-- Button will be inserted here -->
  </div>

  <div id="content-container">
    <!-- Content will be displayed here -->
  </div>

  <script>
    // Function to display template content
    function displayTemplateContent() {
      // Get the content template
      const contentTemplate = document.getElementById('content-template');
      // Access the .content property and clone it
      const contentClone = document.importNode(contentTemplate.content, true);

      // Display the cloned content
      document.getElementById('content-container').appendChild(contentClone);
    }

    // Insert the button template into the DOM
    function insertButton() {
      // Get the button template
      const buttonTemplate = document.getElementById('button-template');
      const buttonClone = document.importNode(buttonTemplate.content, true);

      // Add event listener to the button
      buttonClone.querySelector('#show-content-btn').addEventListener('click', displayTemplateContent);

      // Insert the button into the DOM
      document.getElementById('button-container').appendChild(buttonClone);
    }

    // Call the function to insert the button when the page loads
    insertButton();
  </script>
</body>

En este ejemplo:

  • Tenemos dos plantillas: una para un botón (button-template) y otra para el contenido (content-template).
  • La función insertButton clona la plantilla del botón y la inserta en el DOM. También adjunta un listener de eventos al botón para llamar a la función displayTemplateContent cuando se hace clic.
  • La función displayTemplateContent clona la plantilla de contenido y la inserta en el DOM cada vez que se hace clic en el botón.
  • El botón se inserta en el DOM cuando la página se carga llamando a insertButton.

Así, cuando hagas clic en el botón "Try it yourself", verás un botón etiquetado como "Add a content card." Cada vez que hagas clic en él, se añade una nueva tarjeta de la plantilla de contenido a la página, demostrando la inserción dinámica impulsada por la interacción del usuario.

Errores Comunes

Algunos problemas son responsables de la mayoría de los bugs con plantillas:

  • Olvidarse de clonar. Añadir template.content directamente vacía la plantilla. Siempre clona primero.
  • Consultar después de añadir. appendChild(clone) vacía el fragmento en el DOM, por lo que los nodos del clon se convierten en hijos del destino. Lee o modifica el clon (clone.querySelector(...)) antes de añadirlo — después el fragmento está vacío.
  • IDs duplicados. Los IDs escritos dentro de una plantilla son correctos mientras están inertes, pero una vez que la clonas muchas veces, cada copia tiene el mismo ID — y los IDs duplicados son HTML inválido. Prefiere clases, atributos data-* o consultas limitadas al clon en lugar de document.getElementById para búsquedas por instancia.
  • Esperar que los scripts se ejecuten. Un <script> dentro de una plantilla no se ejecuta al clonar a menos que lo vuelvas a crear. Mantén el comportamiento en tu JavaScript, no en el marcado de la plantilla.

Compatibilidad con Navegadores

El elemento <template> forma parte del HTML Living Standard y está soportado en todos los navegadores modernos (Chrome, Firefox, Safari, Edge). No se necesita ningún polyfill para los navegadores actuales, lo que lo convierte en una opción segura y sin dependencias para la creación de plantillas en el lado del cliente.

Conclusión

El elemento <template> te ofrece una forma nativa y sin frameworks de definir marcado reutilizable e instanciarlo bajo demanda. Dado que su contenido es inerte hasta que se clona, evita el renderizado innecesario y la carga de recursos de los elementos ocultos, y esquiva los problemas de seguridad y análisis de construir HTML desde cadenas de texto. Clona con cloneNode(true) o importNode, rellena el clon y luego añádelo — y tendrás un patrón eficiente que escala desde una sola tarjeta hasta todo un sistema de componentes. Para ver las plantillas dentro de un componente completo, continúa con Elementos Personalizados y Web Components.

Práctica

Práctica
¿Cuál es el propósito principal del Elemento Template en JavaScript?
¿Cuál es el propósito principal del Elemento Template en JavaScript?
Was this page helpful?