# Component UI

Components are how Cicadel renders HTML. You write markup in `.templ` files with typed
Go props and explicit slots, generate Go with the Cicadel CLI, and render from handlers.
They take the place of template files, and the compiler checks every call.

This page is the API contract. To build your first component, start with
[the component walkthrough](https://cicadel.fauzaanu.com/en/dev/howto/component-authoring/index.md).

![A typed component call: props, children and named slots go into a Definition, which validates and calls its compiled templ body to produce escaped HTML. Apps export definitions in their namespace; the project app may override a body while the original contract stays authoritative.](https://github.com/fauzaanu/cicadel/blob/main/docs/diagrams/component-composition.svg)

## Ask your agent

```text
Read https://cicadel.fauzaanu.com/en/dev/topics/component-ui/index.md.
Build a reusable component for [purpose] with typed props and explicit slots.
Show the .go contract, .templ markup and handler call. Use escaped text,
rebuild the generated UI, and test valid and invalid props.
```

## Define a component

A component has three parts: a Go contract, a `.templ` body and a call site.
Keep `props.go` and `card.templ` together in one Go package, often in an app's
`templates/cards/` directory.

The contract declares props, slots, validation and the body:

```go
package cards

import "github.com/fauzaanu/cicadel/ui"

type Props struct {
    Title string
    Compact bool
}
type Slots struct {
    Children ui.Content
    Footer ui.Content
}
var Card = ui.Definition[Props, Slots]{
    Name: "cards:card",
    Source: "templates/cards/card.templ",
    Validate: func(p Props, s Slots) error {
        if err := ui.Required("Title", p.Title); err != nil { return err }
        if s.Children.Empty() { return ui.Required("Children slot", "") }
        return nil
    },
    Body: card,
}
```

The body is ordinary templ markup:

```templ
package cards

templ card(p Props, s Slots) {
    <section>
        <h2>{ p.Title }</h2>
        @s.Children
        if !s.Footer.Empty() {
            <footer>@s.Footer</footer>
        }
    </section>
}
```

A handler calls the definition and renders it:

```go
content := cards.Card.Call(
    cards.Props{Title: "Welcome"},
    cards.Slots{Children: ui.Text("Hello"), Footer: ui.Text("Thanks")},
)
err := (ui.Renderer{}).HTML(r.Context(), w, 200, content)
if err != nil && !ui.Committed(err) { response.Error(w, 500) }
// Log errors privately; do not send diagnostic details to public clients.
```

In markup, insert a component with `@Definition.Call(props, slots)` or `@someContent`.

Private bodies and reusable page composition can also live next to the handlers
that own them, as in the articles example.

### Names and imports

- Imports are ordinary Go module imports and aliases.
- There is no filesystem-based name lookup and no global registry.
- Exported contracts have `appname:component` names, such as `cards:card`.

## Slots and content

A slot is a `ui.Content` field. `ui.Content` is an opaque **HTML fragment**. It is
never a string or an attribute value.

| API | What it does |
| --- | --- |
| `ui.Text(s)` | Escapes a string into content. |
| `ui.Group(a, b, ...)` | Combines fragments into one. |
| `ui.Fragment(c)` | Adapts a trusted compiled component. |
| `ui.Content{}` | Empty content. |
| `c.Empty()` | Reports whether content was supplied. |
| `ui.NoSlots` | Declares a component with no slots. |

`Empty()` reports presence, not rendered length. `ui.Text("")` is a supplied empty
value, so it is not empty.

Name the default child content `Children`. Every other field is a named slot.

There is one model: explicit fields. templ's implicit `{ children... }` context
transport is rejected by the compiler.

## Props, defaults and validation

Go checks field names, types, function arguments, imports and slot compatibility.
Omitted struct fields get Go zero values.

| Hook | When it runs | Use it for |
| --- | --- | --- |
| `Defaults` | First, on a local copy of the props. | Filling in values. |
| `Validate` | Before the component's body and before the HTTP response is committed. | Required values and slot presence. |

- **Required is your rule.** Not every zero value is invalid. You define what is required.
- **Zero versus omitted.** Use pointer or option fields when "omitted" and an explicit
  zero or `false` must differ. Do not treat every zero value as missing.
- **Pure hooks.** Both hooks must be pure. They must not change data the caller references.
- **Overrides keep hooks.** An override body keeps the original `Defaults` and `Validate`.
- **Definitions are configuration.** Treat exported definition variables as immutable.
  Copy one before customizing it.

## Inputs and scope

Cicadel has no context processors and no implicit request map. Pass everything in.

- Dependencies and request data are explicit typed props, function parameters or
  factory arguments.
- A nested body sees only its Go lexical scope and its declared inputs.
- The request `context.Context` carries cancellation and private render bookkeeping.
  Pass business services as explicit typed inputs.
- Use ordinary Go `if`, `switch`, `for` and helpers for variants and collections.
- Every generated markup function receives render frames. This includes private
  bodies and recursive calls.

## Safety and rendering boundary

### Escaping

- templ escapes text expressions and string attributes, across nested components.
- Fragment slots are HTML-body values.
- `title` and `textarea` accept only text and text expressions, not component insertion.
- Mismatched closing tags and unsupported context composition are compile errors.

### URLs

Dynamic URL attributes use templ's contextual URL sanitization. Unsafe values become
`about:invalid#TemplFailedSanitizationURL`.

Use `ui.URL` when you want an error instead:

- **Allows** relative URLs and the `http`, `https`, `mailto` and `tel` schemes.
- **Rejects** control characters, backslashes, surrounding whitespace,
  protocol-relative URLs and every other scheme.

`ui.URL` is not an origin allowlist.

### Forwarding attributes

`ui.Merge` is the forwarding boundary. Pass it ordered `ui.Attributes` groups.

- Names must be lowercase.
- Later values win. Classes are the exception: they merge unique tokens in
  first-seen order.
- Known boolean attributes need `bool` values. `false` omits them.
- Text, `aria-*` and `data-*` values need strings. Use `"false"` for ARIA false.
- URL-like attributes must also pass `ui.URL`.
- Output attribute order is deterministic.
- Merge before spreading. Do not repeat merged keys as literal attributes.

Forwarding is allowlisted. `ui.Merge` rejects:

- event handlers and inline `style`
- `srcdoc` and `srcset`
- namespaced attributes
- arbitrary custom behavior attributes, such as `hx-get`
- unknown standard attributes

Explicit attributes you write in trusted markup are a separate authoring decision.
Contextual URL escaping still applies to them. An application can extend a typed
component's declared API instead of forwarding everything blindly.

### What the source contract rejects

The initial source contract rejects:

- inline script and CSS templates
- `script` and `style` elements
- event-handler attributes and dynamic attribute names
- active foreign or embedded content: `svg`, `math`, `iframe`, `object`, `embed`
  and obsolete raw-text elements

Link static stylesheets explicitly. Client enhancement adapters and active content
are future capabilities. Composition does not imply them.

### Trusted code

> [!WARNING]
> Cicadel has no conversion from strings to trusted HTML, URLs or JavaScript.
> Do not call upstream `templ.Raw` or `SafeURL`, and do not write untrusted strings directly.

`ui.Fragment` is a narrow adapter for **trusted Go implementations**. They must
preserve HTML safety.

Components and packages run trusted Go code. They are not a sandbox and can bypass
any framework contract on purpose. Review them like any other dependency.

## Render a response

`ui.Renderer` renders into a buffer first, then writes.

| Method | Behavior |
| --- | --- |
| `Renderer.Render` | Returns bytes only after a successful render. |
| `Renderer.HTML` | Renders, then sets the content type and status, then writes. |

- A render error leaves the HTTP writer untouched.
- A write failure after commitment is a `*ui.CommitError`. Check it with
  `ui.Committed(err)`. Log it and never try a second response.
- Pass a response writer that has not been committed yet.
- `Renderer` has no streaming or flush API.
- Direct low-level component `Render` calls do not give HTTP atomicity.
- Go panics stay panics. The host's recovery middleware handles them. They do not
  become validation failures.

### Limits

| Limit | Default | Override |
| --- | --- | --- |
| Render depth | 64 frames | `Renderer{MaxDepth: ...}` |
| Output size | 4 MiB | `Renderer{MaxBytes: ...}` |

- Frames include contract calls and markup bodies.
- Cancellation is checked at component boundaries and on buffered writes.
- Go helpers must cooperate with the context. The engine cannot stop an infinite Go computation.
- The output bound excludes caller-owned props and intermediate Go expressions.

### Concurrency and buffers

- No shared compile or render cache holds request values, children, context or output.
- Generated literal writes ignore upstream development-mode environment variables
  and never read source files.
- Generated code and literals are immutable.
- Cicadel uses request-owned buffers. It bypasses templ's global buffer pool on
  purpose, because that pool retains an underlying writer.
- Definitions and registries can be shared. Do not mutate props, slices, maps or
  closures while a render uses them.

## Installable libraries and overrides

Apps register components explicitly. Importing a package or embedding source
activates nothing on its own.

```go
cicadel.App{
    Name: "cards",
    Components: []ui.Export{Card.Export(1)},
}
```

### Exports and namespaces

- `App.Components []ui.Export` registers each `Definition.Export(version)`.
- The app owns its namespace. Duplicate names, invalid exports and nil bodies fail construction.
- Component contract versions are positive integers. They are independent of module versions.
- `Manifest` and `Requires` check app APIs and dependency versions. Go modules select the actual code.

### Assets

- `App.ComponentAssets` lists required files in `App.Static`. Construction checks
  that they exist and are files.
- Static files are served under `/static/<app>/` with content-hashed URLs. See
  [static files](https://cicadel.fauzaanu.com/en/dev/howto/static-files/index.md).
- Compiled Go embeds the markup. `go:embed` packages static resources and can
  package sources for inspection.
- At runtime nothing reads `.templ` files or the working directory.

### Project overrides

Only the **local `project` app** can set `ComponentOverrides`. Build each one with
`Definition.Override(version)`. No package or manifest app can override another
package implicitly.

| Rule | Why |
| --- | --- |
| One override wins over the original. Duplicate overrides fail. | No ambiguity about which body renders. |
| It keeps the exact named Go props and slots types and the contract version. | Callers stay type-compatible. |
| It supplies only the body and source. The original defaults and validation stay authoritative. | A replacement cannot silently weaken required props or slots. |

Semantic HTML behavior still needs application tests.

### Resolve and inject

1. Build a registry with `cicadel.ComponentRegistry(apps)`.
2. Resolve a definition with `ui.Resolve(registry, cards.Card)`.
3. Pass the resolved value into your page or route factories.

`cicadel.New` also validates the registry and exposes it as `Application.Components`.

Calling an imported definition directly uses that definition. No hidden lookup
changes its meaning. Explicit injection keeps project overrides reviewable and
keeps separate hosts independent.

### Example library

The [independent cards module](https://github.com/fauzaanu/cicadel/tree/main/examples/component-library) includes:

- a compiled body with a required child slot and an optional footer
- forwarded attributes
- embedded source and CSS
- an app manifest and HTTP tests

It has no mandatory design-system dependency.

The [generated consumer test](https://github.com/fauzaanu/cicadel/blob/main/internal/scaffold/components_test.go) verifies
real installation. It publishes a unique test version through a local Go proxy,
runs `go get`, installs it into a fresh starter and overrides it explicitly. It
checks escaping and concurrency, then runs compiled HTTP tests and a production
executable outside the source tree.

> [!NOTE]
> This verifies module installation. It does not claim the example is publicly
> published. The local module's framework `replace` is for development only.

## Exact authoring and verification loop

Generated projects pin the CLI through a Go `tool` directive. After `go mod tidy`:

```sh
go doc example.com/your-library.Props
# Edit the props/slots types and .templ body.
go tool cicadel ui css install  # once, when using Tailwind
go tool cicadel ui build
go tool cicadel ui check --json
go test -run '^TestMyPage$' ./apps/myapp
go test -race ./...
go run manage.go check
go build -trimpath -o bin/server .
```

- In the framework checkout, use `go run ./cmd/cicadel ui build` and `go run ./cmd/cicadel ui check`.
- In an existing project, add the matching CLI with
  `go get -tool github.com/fauzaanu/cicadel/cmd/cicadel@VERSION`.
- The tool's compiler and runtime must come from the same framework dependency graph.

### What each command does

| Command | Does | Does not |
| --- | --- | --- |
| `ui generate` | Parses all module sources before writing. Writes deterministic `*_templ.go` files atomically per file, with a source hash and compiler version. | Build CSS. It is markup only. |
| `ui build` | Generates markup and builds configured Tailwind CSS. | — |
| `ui check` | Detects missing, stale and orphan outputs. Checks configured CSS freshness. Compiles all Go packages and tests (`go test -c -o /dev/null ./...`, using the platform null device). | Change generated files, run tests, start a web server or evaluate app factories. |
| `manage.go check` | Constructs and validates the installed registry and resources. | Replace host-specific database setup, which follows the host's construction rules. |

- Commit the generated `*_templ.go` files.
- Production `cicadel.New` validates registered libraries, assets and overrides before serving.
- Run both source checks and host validation in CI.

> [!WARNING]
> A plain `go build` cannot detect that an ignored `.templ` source changed. The
> stale check is a required build step.

### Development and restarts

- `go tool cicadel dev` compiles and restarts the executable automatically. See
  [the foundation workflow](https://cicadel.fauzaanu.com/en/dev/internals/foundation/index.md).
- In-process hot replacement is not supported.
- Manual build, check and restart of `go run manage.go runserver` still work.
- A syntax failure during generation leaves existing outputs intact.
- A failed development build keeps the working server running.
- Concurrent generators in one checkout are unsupported.
- Nested Go modules need their own invocation.
- `.git` and hidden directories, `vendor` and `testdata` are not compiled as application sources.
- Remove orphan generated files explicitly. They are never adopted silently.

### Diagnostics

Commands exit 0 on success and 1 on failure, including invalid arguments.

`--json` prints a JSON array of diagnostics to stdout. It is empty on success. The
CLI also prints a human-readable error to stderr.

| Field | Meaning |
| --- | --- |
| `code` | Stable diagnostic ID. |
| `message` | What went wrong. |
| `file`, `line`, `column` | Source position. One-based. |
| `stack` | Component stack. |

Stable IDs, by when they happen:

| Stage | Code | Meaning |
| --- | --- | --- |
| Compile | `UI_PARSE` | The `.templ` file could not be parsed. |
| Compile | `UI_SYNTAX` | A syntax error at a reported line and column. |
| Compile | `UI_GENERATE` | Go generation from the template failed. |
| Compile | `UI_TYPE` | The generated Go does not type-check. |
| Compile | `UI_BUILD` | The package containing components does not build. |
| Check | `UI_STALE` | Generated output is missing or out of date. Run `cicadel ui generate`. |
| Check | `UI_ORPHAN` | A generated file has no `.templ` source. Remove it. |
| Registry | `UI_NAMESPACE` | An exported component name is invalid in its namespace. |
| Registry | `UI_COLLISION` | Two components or overrides share a name. |
| Registry | `UI_UNKNOWN` | An override targets a component that does not exist. |
| Registry | `UI_CONTRACT` | An override or resolve does not match the original contract. |
| Render | `UI_ATTRIBUTE` | An attribute name is invalid or not lowercase. |
| Render | `UI_URL` | A URL is unsafe or contains control characters. |
| Render | `UI_LIMIT` | Output exceeded the render byte limit. |
| Render | `UI_RENDER` | A component failed while rendering. |
| Render | `UI_WRITE` | Writing the response failed after output started. |

Tailwind has its own `UI_CSS_*` codes; see [the Tailwind guide](https://cicadel.fauzaanu.com/en/dev/howto/tailwind/index.md).

- Runtime diagnostics implement `error` and `Unwrap`, and can be JSON-encoded.
- Nested errors keep their deepest component stack.
- Source parse and render expression locations are precise.
- Go type errors map to the owning `.templ` declaration. They also keep the exact
  generated Go position and message.
- Plain Go errors keep their Go source position.

> [!WARNING]
> Error messages can contain input values. Keep them in development tooling and private logs.

## Forms and layouts

| Component | Provides |
| --- | --- |
| `ui/layout.Page` | A minimal document shell with explicit header, children, footer and scripts slots, plus an optional explicit stylesheet URL. `Lang` and `Dir` set `<html lang dir>` (for example `Lang: "dv", Dir: "rtl"` for Thaana pages; defaults `en` and `ltr`, validated). `CSRFToken` makes htmx requests send `X-CSRF-Token`. |
| `ui/form.Field` | Associated labels, bound values, `required` and `maxlength`, password/text/textarea variants, and error-only `aria-invalid` and `aria-describedby`. |
| `ui/form.CSRF` | A hidden CSRF field. It requires an explicit token from the session middleware. |

Components do not create tokens, validate requests or bypass CSRF. Binding and
validation stay in `forms` and your application.

The starter, the articles list/detail/login/edit pages and the catalog/admin pages
use these compiled components. Their authorization, ownership filtering, CSRF and
persistence paths are unchanged. Real PostgreSQL HTTP regression tests verify them.

FORM-01 is still Partial. Richer typed binding, validators, widgets and ModelForms
are separate work. The broader administration battery is not completed here either.

Application UI uses compiled `.templ` components. Template strings, global
definitions and `template.HTML` are not component content. Follow the
[component authoring guide](https://cicadel.fauzaanu.com/en/dev/howto/component-authoring/index.md).

## htmx and REST

Use the bundled [htmx 4 integration](https://cicadel.fauzaanu.com/en/dev/howto/htmx/index.md) for component HTML requests. Keep
REST/JSON handlers for API clients.

To load the embedded runtime, pass `htmx.Script()` to the standard page layout's
`Scripts` slot and mount `htmx.Handler()`. No CDN, npm or deployment-time asset
collection is required.

## Acceptance checklist

These checks cover the initial server component contract on this page, not upstream
templ features Cicadel excludes. Commands and evidence are recorded in
[component verification](https://cicadel.fauzaanu.com/en/dev/sources/docs/component-verification.md).

- [x] Syntax, layout, Go compiler and runtime API, props, explicit slots and scope.
- [x] Deterministic generated artifacts and source diagnostics. Unknown components,
  invalid nesting or context, and incompatible props or slots are rejected.
- [x] Typed props, required validation, defaults, explicit dependencies, children,
  named slots, nesting, conditionals, collections and reusable layouts.
- [x] Safe forwarding and merging, and contextual escaping, including malicious inputs.
- [x] Accessible reusable form components and explicit CSRF integration.
- [x] Package namespaces, collision checks, exact compatible overrides, required
  embedded assets, app dependency versions and component contract versions.
- [x] Development regeneration and restart, nested render stacks, stale checks and
  production registry validation without JavaScript tooling.
- [x] Buffered HTTP failures, committed write errors, concurrency, cancellation,
  recursion bounds, output limits and request-owned buffers.
- [x] Starter, articles and catalog/admin pages built from reusable components.
- [x] An independent library installed into a generated app with typed props,
  children, named slots, an explicit override, security and isolation tests, and
  production checks.
