Templ Avanzado: Layouts y Composicion

Por: Artiko
templgolayoutscomposicioncss

Capitulo 6: Templ Avanzado - Layouts y Composicion

En este capitulo exploramos las capacidades avanzadas de Templ: componentes con children, layouts, CSS integrado y patrones de organizacion para proyectos reales.

Componentes con children

Un componente puede recibir contenido hijo usando children dentro de la firma:

package layouts

templ Card() {
	<div class="card">
		<div class="card-body">
			{ children... }
		</div>
	</div>
}

Al usarlo, el contenido se pasa entre las etiquetas del componente:

package pages

import "myapp/layouts"

templ HomePage() {
	@layouts.Card() {
		<h2>Titulo de la card</h2>
		<p>Contenido interno que se inyecta en children.</p>
	}
}

Crear un layout base

Los layouts son componentes con children que definen la estructura HTML completa:

package layouts

templ Base(title string) {
	<!DOCTYPE html>
	<html lang="es">
		<head>
			<meta charset="UTF-8"/>
			<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
			<title>{ title }</title>
			<link rel="stylesheet" href="/static/styles.css"/>
		</head>
		<body>
			@Nav()
			<main class="container">
				{ children... }
			</main>
			@Footer()
		</body>
	</html>
}

templ Nav() {
	<nav class="navbar">
		<a href="/">Inicio</a>
		<a href="/about">Acerca de</a>
	</nav>
}

templ Footer() {
	<footer class="footer">
		<p>Mi aplicacion Go + Templ</p>
	</footer>
}

Composicion: pagina dentro de layout

Cada pagina usa el layout base y define su propio contenido:

package pages

import "myapp/layouts"

templ Home() {
	@layouts.Base("Inicio") {
		<h1>Bienvenido</h1>
		<p>Esta es la pagina principal.</p>
	}
}

templ About() {
	@layouts.Base("Acerca de") {
		<h1>Acerca de nosotros</h1>
		<p>Informacion sobre el proyecto.</p>
	}
}

El patron es siempre el mismo: el layout envuelve, la pagina llena el contenido.

CSS en Templ

Templ ofrece dos formas de manejar CSS directamente en los componentes.

CSS con clases generadas

Puedes definir estilos con scope automatico usando css:

package components

css cardStyle() {
	background-color: #ffffff;
	border-radius: 8px;
	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
	padding: 1rem;
}

templ StyledCard(title string) {
	<div class={ cardStyle() }>
		<h2>{ title }</h2>
		{ children... }
	</div>
}

Templ genera nombres de clase unicos para evitar colisiones.

CSS con parametros

Los bloques css pueden recibir parametros para estilos dinamicos:

package components

css coloredText(color string) {
	color: { color };
	font-weight: bold;
}

templ Alert(message string, color string) {
	<p class={ coloredText(color) }>{ message }</p>
}

Atributos dinamicos y clases condicionales

Puedes construir atributos dinamicamente:

package components

templ Button(text string, primary bool, disabled bool) {
	<button
		class={ "btn", templ.KV("btn-primary", primary), templ.KV("btn-disabled", disabled) }
		disabled?={ disabled }
	>
		{ text }
	</button>
}

templ.KV agrega la clase solo si la condicion es true. El operador ?= en atributos booleanos renderiza el atributo solo cuando el valor es true.

Atributos spread

Para pasar atributos arbitrarios:

package components

templ Input(inputType string, name string, attrs templ.Attributes) {
	<input type={ inputType } name={ name } { attrs... }/>
}

Uso desde otro componente:

@components.Input("text", "email", templ.Attributes{
	"placeholder": "[email protected]",
	"required":    true,
})

Componentes reutilizables

Componente boton

package ui

templ Btn(text string, variant string) {
	<button class={ "btn", "btn-" + variant }>
		{ text }
	</button>
}

Componente card reutilizable

package ui

templ InfoCard(title string, subtitle string) {
	<div class="card">
		<div class="card-header">
			<h3>{ title }</h3>
			if subtitle != "" {
				<p class="subtitle">{ subtitle }</p>
			}
		</div>
		<div class="card-content">
			{ children... }
		</div>
	</div>
}

Componente input con label

package ui

templ FormField(label string, name string, inputType string) {
	<div class="form-field">
		<label for={ name }>{ label }</label>
		<input type={ inputType } id={ name } name={ name }/>
	</div>
}

Pasar funciones como props

Puedes pasar funciones Go como parametros para callbacks o generadores de contenido:

package components

templ List(items []string, renderItem func(string) templ.Component) {
	<ul>
		for _, item := range items {
			<li>
				@renderItem(item)
			</li>
		}
	</ul>
}

Uso:

package pages

import "myapp/components"

templ boldItem(text string) {
	<strong>{ text }</strong>
}

templ ItemsPage(items []string) {
	@components.List(items, boldItem)
}

Este patron permite desacoplar la estructura de la lista de como se renderiza cada item.

Organizar archivos .templ en packages

Para proyectos medianos y grandes, la organizacion por responsabilidad es clave:

project/
├── main.go
├── handlers/
│   ├── home.go
│   ├── users.go
│   └── api.go
├── templates/
│   ├── layouts/
│   │   ├── base.templ
│   │   └── admin.templ
│   ├── pages/
│   │   ├── home.templ
│   │   ├── about.templ
│   │   └── users.templ
│   ├── components/
│   │   ├── nav.templ
│   │   ├── footer.templ
│   │   └── card.templ
│   └── ui/
│       ├── button.templ
│       ├── input.templ
│       └── modal.templ
├── static/
│   └── styles.css
├── go.mod
└── go.sum

Cada directorio dentro de templates/ es un package Go independiente. Importas lo que necesitas:

package pages

import (
	"myapp/templates/layouts"
	"myapp/templates/components"
)

templ Dashboard() {
	@layouts.Base("Dashboard") {
		@components.Card() {
			<h1>Panel de control</h1>
		}
	}
}

Script tags y JavaScript inline

Cuando necesitas JavaScript, Templ permite incluir scripts:

package components

script toggleMenu() {
	const menu = document.getElementById("mobile-menu");
	menu.classList.toggle("hidden");
}

templ MenuButton() {
	<button onclick={ toggleMenu() }>Menu</button>
	<div id="mobile-menu" class="hidden">
		<a href="/">Inicio</a>
		<a href="/about">Acerca de</a>
	</div>
}

Los bloques script generan funciones JavaScript que se deduplican automaticamente: si el componente se renderiza multiples veces, el script solo se incluye una vez.

Para JavaScript mas complejo, es mejor usar archivos .js externos y referenciarlos desde el layout:

package layouts

templ Base(title string) {
	<!DOCTYPE html>
	<html lang="es">
		<head>
			<title>{ title }</title>
		</head>
		<body>
			{ children... }
			<script src="/static/app.js"></script>
		</body>
	</html>
}

Anterior: Introduccion a Templ | Siguiente: Introduccion a HTMX