The component layer
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.
#Ask your agent
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:
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:
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:
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:componentnames, such ascards: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
falsemust 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
DefaultsandValidate. - 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.Contextcarries cancellation and private render bookkeeping. Pass business services as explicit typed inputs. - Use ordinary Go
if,switch,forand 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.
titleandtextareaaccept 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,mailtoandtelschemes. - 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
boolvalues.falseomits them. - Text,
aria-*anddata-*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 srcdocandsrcset- 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
scriptandstyleelements- event-handler attributes and dynamic attribute names
- active foreign or embedded content:
svg,math,iframe,object,embedand 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 withui.Committed(err). Log it and never try a second response. - Pass a response writer that has not been committed yet.
Rendererhas no streaming or flush API.- Direct low-level component
Rendercalls 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.
cicadel.App{
Name: "cards",
Components: []ui.Export{Card.Export(1)},
}#Exports and namespaces
App.Components []ui.Exportregisters eachDefinition.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.
ManifestandRequirescheck app APIs and dependency versions. Go modules select the actual code.
#Assets
App.ComponentAssetslists required files inApp.Static. Construction checks that they exist and are files.- Static files are served under
/static/<app>/with content-hashed URLs. See static files. - Compiled Go embeds the markup.
go:embedpackages static resources and can package sources for inspection. - At runtime nothing reads
.templfiles 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
- Build a registry with
cicadel.ComponentRegistry(apps). - Resolve a definition with
ui.Resolve(registry, cards.Card). - 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 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 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.
Good to know
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:
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 buildandgo 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.gofiles. - Production
cicadel.Newvalidates 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 devcompiles and restarts the executable automatically. See the foundation workflow.- In-process hot replacement is not supported.
- Manual build, check and restart of
go run manage.go runserverstill 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.
.gitand hidden directories,vendorandtestdataare 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.
- Runtime diagnostics implement
errorandUnwrap, 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
.templdeclaration. 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.
#htmx and REST
Use the bundled htmx 4 integration 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.
- Syntax, layout, Go compiler and runtime API, props, explicit slots and scope.
- Deterministic generated artifacts and source diagnostics. Unknown components, invalid nesting or context, and incompatible props or slots are rejected.
- Typed props, required validation, defaults, explicit dependencies, children, named slots, nesting, conditionals, collections and reusable layouts.
- Safe forwarding and merging, and contextual escaping, including malicious inputs.
- Accessible reusable form components and explicit CSRF integration.
- Package namespaces, collision checks, exact compatible overrides, required embedded assets, app dependency versions and component contract versions.
- Development regeneration and restart, nested render stacks, stale checks and production registry validation without JavaScript tooling.
- Buffered HTTP failures, committed write errors, concurrency, cancellation, recursion bounds, output limits and request-owned buffers.
- Starter, articles and catalog/admin pages built from reusable components.
- An independent library installed into a generated app with typed props, children, named slots, an explicit override, security and isolation tests, and production checks.