| name | templ-components |
| description | Create reusable templ UI components with props, children, and composition patterns. Use when building UI components, creating component libraries, mentions 'button component', 'card component', or 'reusable templ components'. |
Templ Components
Overview
Build reusable, type-safe UI components with templ. Components accept strongly-typed props and can be composed together to create complex UIs.
When to Use This Skill
Use when:
- Creating reusable UI components
- Building component libraries
- User mentions "button", "card", "form", "modal" components
- Designing component APIs
- Working on design systems
Core Component Patterns
Basic Component
package components
templ Button(text string) {
<button class="btn">
{ text }
</button>
}
Component with Multiple Props
templ Button(text string, variant string, disabled bool) {
<button
class={ "btn btn-" + variant }
disabled?={ disabled }
>
{ text }
</button>
}
Usage:
components.Button("Submit", "primary", false)
Component with Children
templ Card(title string) {
<div class="card">
<div class="card-header">
<h3>{ title }</h3>
</div>
<div class="card-body">
{ children... }
</div>
</div>
}
Usage:
@Card("User Profile") {
<p>Name: John Doe</p>
<p>Email: john@example.com</p>
}
Component with Struct Props
package components
type ButtonProps struct {
Text string
Variant string
Disabled bool
OnClick string
}
templ Button(props ButtonProps) {
<button
class={ "btn btn-" + props.Variant }
disabled?={ props.Disabled }
onclick={ props.OnClick }
>
{ props.Text }
</button>
}
Common Components
Button Variants
templ PrimaryButton(text string) {
<button class="btn btn-primary">{ text }</button>
}
templ SecondaryButton(text string) {
<button class="btn btn-secondary">{ text }</button>
}
templ DangerButton(text string, onclick string) {
<button class="btn btn-danger" onclick={ onclick }>
{ text }
</button>
}
Card Component
templ Card(title string, footer string) {
<div class="card">
if title != "" {
<div class="card-header">
<h3>{ title }</h3>
</div>
}
<div class="card-body">
{ children... }
</div>
if footer != "" {
<div class="card-footer">
{ footer }
</div>
}
</div>
}
List Component
type ListItem struct {
ID string
Text string
}
templ List(items []ListItem) {
<ul class="list">
for _, item := range items {
<li data-id={ item.ID }>
{ item.Text }
</li>
}
</ul>
}
Modal Component
templ Modal(id string, title string, isOpen bool) {
<div
class={ "modal", templ.KV("modal-open", isOpen) }
id={ id }
>
<div class="modal-backdrop"></div>
<div class="modal-content">
<div class="modal-header">
<h2>{ title }</h2>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
{ children... }
</div>
</div>
</div>
}
Form Components
templ Input(name string, label string, value string) {
<div class="form-group">
<label for={ name }>{ label }</label>
<input
type="text"
id={ name }
name={ name }
value={ value }
class="form-control"
/>
</div>
}
templ TextArea(name string, label string, rows int) {
<div class="form-group">
<label for={ name }>{ label }</label>
<textarea
id={ name }
name={ name }
rows={ strconv.Itoa(rows) }
class="form-control"
>
{ children... }
</textarea>
</div>
}
templ Select(name string, label string, options []string) {
<div class="form-group">
<label for={ name }>{ label }</label>
<select id={ name } name={ name } class="form-control">
for _, option := range options {
<option value={ option }>{ option }</option>
}
</select>
</div>
}
Layout Components
Container
templ Container(fluid bool) {
<div class={ templ.KV("container", !fluid), templ.KV("container-fluid", fluid) }>
{ children... }
</div>
}
Grid
templ Row() {
<div class="row">
{ children... }
</div>
}
templ Col(size int) {
<div class={ "col-" + strconv.Itoa(size) }>
{ children... }
</div>
}
Usage:
@Container(false) {
@Row() {
@Col(6) {
<p>Left column</p>
}
@Col(6) {
<p>Right column</p>
}
}
}
Navigation
type NavItem struct {
Text string
Href string
Active bool
}
templ Nav(items []NavItem) {
<nav class="navbar">
<ul class="nav">
for _, item := range items {
<li class={ templ.KV("nav-item-active", item.Active) }>
<a href={ templ.URL(item.Href) }>
{ item.Text }
</a>
</li>
}
</ul>
</nav>
}
Composition Patterns
Slots Pattern
templ Layout(title string, headerContent templ.Component, footerContent templ.Component) {
<!DOCTYPE html>
<html>
<head>
<title>{ title }</title>
</head>
<body>
<header>
@headerContent
</header>
<main>
{ children... }
</main>
<footer>
@footerContent
</footer>
</body>
</html>
}
Render Props Pattern
templ DataTable(headers []string, renderRow func(int) templ.Component) {
<table>
<thead>
<tr>
for _, header := range headers {
<th>{ header }</th>
}
</tr>
</thead>
<tbody>
for i := 0; i < 10; i++ {
@renderRow(i)
}
</tbody>
</table>
}
Wrapper Components
templ WithLoading(isLoading bool) {
if isLoading {
<div class="spinner">Loading...</div>
} else {
{ children... }
}
}
templ WithError(err error) {
if err != nil {
<div class="alert alert-error">
{ err.Error() }
</div>
} else {
{ children... }
}
}
Best Practices
1. Single Responsibility
// ✅ Good: One purpose
templ Avatar(src string, alt string) {
<img src={ src } alt={ alt } class="avatar" />
}
// ❌ Bad: Too many responsibilities
templ UserSection(user User, posts []Post, comments []Comment) {
// Too much in one component
}
2. Type-Safe Props
// ✅ Good: Strongly typed
type ButtonProps struct {
Text string
Variant ButtonVariant
}
type ButtonVariant string
const (
Primary ButtonVariant = "primary"
Secondary ButtonVariant = "secondary"
)
templ Button(props ButtonProps) {
<button class={ "btn btn-" + string(props.Variant) }>
{ props.Text }
</button>
}
3. Composition Over Complexity
// ✅ Good: Compose small components
templ UserCard(user User) {
@Card(user.Name) {
@Avatar(user.AvatarURL, user.Name)
@UserInfo(user)
@UserActions(user.ID)
}
}
// Each sub-component is simple and reusable
4. Conditional Rendering
// ✅ Good: Clear conditions
templ Message(text string, isError bool) {
if isError {
<div class="alert-error">{ text }</div>
} else {
<div class="alert-info">{ text }</div>
}
}
5. Default Props
// In Go code
type ButtonProps struct {
Text string
Variant string
Disabled bool
}
func NewButton(text string) ButtonProps {
return ButtonProps{
Text: text,
Variant: "primary",
Disabled: false,
}
}
templ Button(props ButtonProps) {
<button
class={ "btn btn-" + props.Variant }
disabled?={ props.Disabled }
>
{ props.Text }
</button>
}
Package Organization
// components/ui/button.templ
package ui
templ Button(text string) {
<button class="btn">{ text }</button>
}
// components/layout/container.templ
package layout
templ Container() {
<div class="container">
{ children... }
</div>
}
Usage:
import (
"myapp/components/ui"
"myapp/components/layout"
)
layout.Container() {
ui.Button("Click me")
}
Testing Components
func TestButton(t *testing.T) {
var buf bytes.Buffer
props := ButtonProps{
Text: "Submit",
Variant: "primary",
}
err := Button(props).Render(context.Background(), &buf)
if err != nil {
t.Fatal(err)
}
html := buf.String()
if !strings.Contains(html, "Submit") {
t.Error("Button text not found")
}
if !strings.Contains(html, "btn-primary") {
t.Error("Primary class not found")
}
}
Next Steps
- Connect to server → Use
templ-httpskill - Add interactivity → Use
templ-htmxskill - Style components → Use
templ-cssskill