> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lightning-go.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# HTML Templates

> Render server-side HTML using Go's standard text/template engine with custom template functions.

Lightning uses Go's standard `text/template` package for server-side rendering. Load templates once at startup with `app.LoadHTMLGlob()`, then render them from any handler with `ctx.HTML()`.

## Loading Templates

Call `app.LoadHTMLGlob(pattern)` before starting the server. The pattern follows the same glob syntax as `filepath.Glob`.

```go theme={null}
app.LoadHTMLGlob("templates/*.html")
```

<Warning>
  `LoadHTMLGlob` panics if the pattern matches no files or if any template fails to parse. Always verify your template paths before deploying.
</Warning>

## Custom Template Functions

Register custom functions with `app.SetFuncMap()` **before** calling `app.LoadHTMLGlob()`. The function map is applied when templates are parsed.

```go theme={null}
import (
    "fmt"
    "text/template"
    "time"

    "github.com/go-labx/lightning"
)

func formatDate(t time.Time) string {
    year, month, day := t.Date()
    return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}

func main() {
    app := lightning.DefaultApp()

    // Register custom functions first
    app.SetFuncMap(template.FuncMap{
        "formatDate": formatDate,
    })

    // Then load templates (so they can use the functions)
    app.LoadHTMLGlob("templates/*.tmpl")
}
```

## Rendering a Template

Call `ctx.HTML(code, name, data)` from a handler. `name` is the template file name as registered by the glob, and `data` is any value passed as the template's dot (`.`).

```go theme={null}
app.Get("/", func(ctx *lightning.Context) {
    ctx.HTML(lightning.StatusOK, "index.tmpl", lightning.Map{
        "title":       "My App",
        "description": "Built with Lightning",
        "now":         time.Now(),
    })
})
```

If template execution fails, Lightning responds with `500` and the error message as plain text.

## Complete Example

<Steps>
  <Step title="Create the template file">
    **`templates/index.tmpl`:**

    ```html theme={null}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{.title}}</title>
        <link rel="stylesheet" href="/static/css/style.css" />
    </head>
    <body>
        <h1>{{.title}}</h1>
        <p>{{.description}}</p>
        <p>Today is {{.now | formatDate}}</p>
    </body>
    </html>
    ```
  </Step>

  <Step title="Write the server code">
    **`main.go`:**

    ```go theme={null}
    package main

    import (
        "fmt"
        "text/template"
        "time"

        "github.com/go-labx/lightning"
    )

    func formatDate(t time.Time) string {
        year, month, day := t.Date()
        return fmt.Sprintf("%d-%02d-%02d", year, month, day)
    }

    func main() {
        app := lightning.DefaultApp()

        // Serve CSS/JS from ./public at /static
        app.Static("./public", "/static")

        // Register custom template functions before loading
        app.SetFuncMap(template.FuncMap{
            "formatDate": formatDate,
        })

        // Load all templates from the templates directory
        app.LoadHTMLGlob("templates/*.tmpl")

        app.Get("/", func(ctx *lightning.Context) {
            ctx.HTML(lightning.StatusOK, "index.tmpl", lightning.Map{
                "title":       "Lightning",
                "description": "A fast web framework for Go.",
                "now":         time.Now(),
            })
        })

        app.Run(":8080")
    }
    ```
  </Step>

  <Step title="Run and visit the page">
    ```bash theme={null}
    go run main.go
    # open http://localhost:8080
    ```
  </Step>
</Steps>

<Tip>
  Use `lightning.Map` (a `map[string]any` alias) to pass ad-hoc template data without defining a dedicated struct.
</Tip>
