# Composable component UI

Write UI in `.templ` files with typed Go props and explicit slots. Generate the Go files with your project’s Cicadel CLI, then render them from HTTP handlers. Start with [the component walkthrough](https://cicadel.fauzaanu.com/en/dev/howto/component-authoring/index.md).

## 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.
```

## Authoring and public contracts

Keep `props.go` and `card.templ` together in a Go package, commonly beneath an
app's `templates/cards/` directory. Private bodies and reusable page composition
may also live alongside the owning handlers, as in the articles example. Imports
are ordinary Go module imports and aliases; no filesystem-based name resolution
or global registry occurs. Exported contracts have `appname:component` names.

```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,
}
```

```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>
}
```

```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.
```

`ui.Content` is an opaque **HTML fragment**, never a string or attribute value.
`ui.Text` escapes strings; `ui.Group` composes fragments; `ui.Fragment` adapts a
trusted compiled component. `Content{}` is empty; `ui.Text("")` is a supplied empty
text value, so `Empty()` reports presence rather than rendered text length.
Use a named `Children` field for default child content, with other fields for
named slots. `ui.NoSlots` declares no slots. There is one model: explicit fields,
not templ's implicit `{ children... }` context transport (rejected by the compiler).
Use `@someContent` or `@Definition.Call(props, slots)` in markup.

Go checks field names, types, function arguments, imports, and slot compatibility.
Omitted struct fields have Go zero values. `Validate` declares required values and
slot presence; it runs before that component's body and before HTTP commitment.
Required does not mean every zero value is invalid: the author specifies the
semantic rule. `Defaults`, if provided, runs first on a local props copy. Use
pointer/option fields when omitted and explicitly zero/false must differ; do not
silently treat all zero values as missing. Both hooks must be pure and must not
mutate referenced caller data. Override bodies retain the original default and
validation hooks. Exported definition variables are configuration values: treat
them as immutable; copy before customizing.

Dependencies and request data are explicit typed props, function parameters, or
factory arguments. There are no context processors or implicit request maps.
A nested body sees only its Go lexical scope and declared inputs. Request
`context.Context` carries cancellation and private render bookkeeping; business
services should be explicit typed inputs. Ordinary Go `if`, `switch`, `for`, and
helpers support variants and collections. All generated markup functions,
including private bodies and recursive calls, receive render frames.

## Safety and rendering boundary

- Text expressions and string attributes are escaped by templ across nested
  components. Fragment slots are HTML-body values. `title` and `textarea` permit
  only text expressions/text, not component insertion. Markup mismatched closing
  tags and unsupported context composition are compile errors.
- Dynamic URL attributes use templ's contextual URL sanitization; unsafe values
  become `about:invalid#TemplFailedSanitizationURL`. Use `ui.URL` when the caller
  needs an error instead. It allows relative URLs and `http`, `https`, `mailto`,
  and `tel`; rejects control characters, backslashes, whitespace padding,
  protocol-relative URLs, and other schemes. This is not an origin allowlist.
- `ui.Merge` is the forwarding boundary. Pass ordered `ui.Attributes` groups;
  names must be lowercase. Later values win, except classes merge unique tokens
  in first-seen order. Known boolean attributes require bools (false omits them).
  Text, `aria-*`, and `data-*` values require strings; use `"false"` for ARIA false.
  URL-like attributes also pass `ui.URL`. Output attribute order is deterministic.
  Merge before spreading and do not duplicate those keys as literal attributes.
- Forwarding is allowlisted. Events, inline style, `srcdoc`, `srcset`, namespaces,
  arbitrary custom behavior attributes such as `hx-get`, and unknown standard
  attributes are rejected. Explicit attributes in trusted markup are a separate
  authoring decision; contextual URL escaping still applies. Applications may
  extend a typed component's declared API rather than blindly forward everything.
- Inline script/CSS templates, script/style elements, event-handler attributes,
  dynamic attribute names, and active foreign/embedded content (`svg`, `math`,
  `iframe`, `object`, `embed`, and obsolete raw-text elements) are outside the initial source contract and rejected.
  Use explicit static stylesheet links. Client enhancement adapters and active
  content support are future capabilities, not implied by composition.
- Cicadel supplies no string-to-trusted-HTML/URL/JS conversion. `ui.Fragment` is a
  narrow adapter for **trusted Go implementations**, which must preserve HTML
  safety. Do not call upstream `templ.Raw`, `SafeURL`, or write untrusted strings
  directly. Components/packages execute trusted Go code, not a sandbox; they can
  bypass any framework contract deliberately. Review them like other dependencies.

`Renderer.Render` returns bytes only after success. `Renderer.HTML` renders first,
then sets content type/status and writes. A render error leaves the HTTP writer
untouched. A write failure after commitment is `*ui.CommitError`;
`ui.Committed(err)` distinguishes it. Log that error, never try a second response.
The caller must supply an uncommitted response writer. No streaming/flush API is
supported through `Renderer`; direct low-level component `Render` calls do not
provide HTTP atomicity. Go panics remain panics and are handled by the host's
existing recovery middleware, not converted into validation failures.

Defaults are **64 render frames** and **4 MiB output**; override with
`Renderer{MaxDepth: ..., MaxBytes: ...}`. Frames include contract calls and markup
bodies. Cancellation is checked at component boundaries and buffered writes.
Arbitrary Go helpers must cooperate with context; the engine cannot preempt an
infinite Go computation. The output bound excludes caller-owned props and
intermediate Go expressions. No shared compile/render cache owns request values,
children, context, or output. Generated literal writes ignore upstream development-mode environment variables and never consult source files. Generated code/literals are immutable; Cicadel uses
request-owned buffers, deliberately bypassing templ's global buffer pool, which
retains an underlying writer. Definitions/registries may be shared; the caller
must not mutate props, slices, maps, or closures while a render uses them.

## Installable libraries and overrides

`App.Components []ui.Export` explicitly registers `Definition.Export(version)`.
The app owns the namespace, and duplicate names, invalid exports, or nil bodies
fail construction. `Manifest` / `Requires` provide app API and dependency-version
checks; Go modules select actual code. Component contract versions are positive
integers, independent of module versions. Nothing is activated by importing a
package or embedding source alone.

`App.ComponentAssets` lists required files in `App.Resources`; construction checks
they exist and are files. Serving is still an explicit route/static-host choice.
Compiled Go embeds markup; `go:embed` packages static resources and may package
sources for inspection. Runtime does not read `.templ` files or the working directory.

The **local `project` app** may supply `ComponentOverrides`. No package or manifest
app can override another package implicitly. One override wins over the original;
duplicate overrides fail. It must retain the exact named Go props/slots types and
contract version. The replacement supplies only the body/source: original default
and validation behavior remains authoritative. This prevents silent required-prop
or required-slot weakening. Semantic HTML behavior still requires application tests.

Build a registry with `cicadel.ComponentRegistry(apps)`, resolve a definition with
`ui.Resolve(registry, cards.Card)`, and pass that resolved value into page/route
factories. `cicadel.New` also validates and exposes `Application.Components`.
Calling an imported definition directly deliberately uses that definition; there
is no hidden lookup that changes its meaning. This explicit injection makes
project overrides reviewable and separate hosts independent.

The [independent cards module](https://github.com/fauzaanu/cicadel/blob/main/examples/component-library) includes a compiled
body, required child slot, optional footer, forwarded attributes, embedded source
and CSS, 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) publishes a
unique test version through a local Go proxy, performs real `go get`, installs it
into a fresh starter, explicitly overrides it, checks escaping and concurrency,
and runs compiled HTTP tests plus a production executable outside the source tree.
This is verified module installation, not a claim that the example is publicly
published. The local module's framework `replace` is 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`. For 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 be from the same framework dependency graph.

`generate` parses all module sources before writing outputs. It writes deterministic
`*_templ.go` files atomically per file, including a source hash and compiler version;
commit them. `build` generates markup and builds configured Tailwind CSS; `generate` remains markup-only.
`check` leaves generated files unchanged: it detects missing/stale/orphan outputs,
checks configured CSS freshness, and
compiles all Go packages/tests (`go test -c -o /dev/null ./...`, using the platform null device). It does not execute tests,
start a web server, or evaluate arbitrary app factories. `manage.go check` constructs
and validates the actual installed registry/resources; host-specific database setup
still follows that host's existing construction rules. Production `cicadel.New`
validates registered libraries, assets, and overrides before serving. Run both
source and host validation in CI. A plain `go build` cannot detect that an ignored
`.templ` source changed: stale checking is a required build step.

Automatic compilation and executable restart are available through `go tool
cicadel dev`; 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` remain supported. Failed generation leaves existing outputs intact for
syntax failures, and a failed development build keeps the working server running.
Concurrent generators in one checkout are unsupported. Nested Go
modules need their own invocation; `.git`/hidden directories, `vendor`, and `testdata`
are not compiled as application sources. Orphan generated files must be removed
explicitly, never silently adopted.

Commands exit 0 on success and 1 on failures, including invalid arguments. `--json`
emits a JSON array of diagnostics to stdout (empty on success); a human error is
also printed to stderr by the CLI. Fields: `code`, `message`, `file`, `line`,
`column`, `stack`. Positions are one-based. Stable IDs include `UI_PARSE`,
`UI_SYNTAX`, `UI_GENERATE`, `UI_TYPE`, `UI_STALE`, `UI_ORPHAN`, `UI_BUILD`,
`UI_NAMESPACE`, `UI_COLLISION`, `UI_UNKNOWN`, `UI_CONTRACT`, `UI_ATTRIBUTE`,
`UI_URL`, `UI_RENDER`, `UI_LIMIT`, the [Tailwind `UI_CSS_*` errors](https://cicadel.fauzaanu.com/en/dev/howto/tailwind/index.md), and `UI_WRITE` (committed transport failure).
Runtime diagnostics implement `error`/`Unwrap` and can be JSON-encoded. Nested
errors retain their deepest component stack. Source parse/render expression
locations are precise; Go type errors map to the owning `.templ` declaration and
preserve the exact generated Go position/message for detailed diagnosis. Plain Go
errors retain their Go source position. Error messages may contain input values;
keep them in development tooling/private logs.

## Forms, application integration, and migration

`ui/layout.Page` provides an optional minimal document shell with explicit header,
children, and footer, plus an optional explicit stylesheet URL. `ui/form.Field` renders associated labels, bound values,
required/maxlength, password/text/textarea variants, and error-only `aria-invalid`
and `aria-describedby` relationships. `ui/form.CSRF` requires an explicit token
from session middleware. Components do not create tokens, validate requests, or
bypass CSRF. Binding/validation stays in `forms` and the application.

The starter, articles list/detail/login/edit, and catalog/admin pages use these
compiled components. Existing authorization, ownership filtering, CSRF, and
persistence paths remain; real PostgreSQL HTTP regression tests verify them.
FORM-01 is still Partial: richer typed binding, validators, widgets and ModelForms
are separate work. Administration's broader battery is also not completed here.

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

## htmx and REST

Use the bundled [htmx 4 integration](https://cicadel.fauzaanu.com/en/dev/howto/htmx/index.md) for component HTML requests,
or keep REST/JSON handlers for API clients. The standard page layout accepts
a `Scripts` slot; pass `htmx.Script()` and mount `htmx.Handler()` to load the
embedded runtime. No CDN, npm, or deployment-time asset collection is required.
