cicadel

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.

How a typed Cicadel component composesProps, a Children slot and named slots go into a ui.Definition call. The definition runs Defaults, then Validate, then its body compiled from .templ into generated Go, and the renderer returns escaped HTML. Below, apps export definitions under their namespace into a registry. Only the local project app may override a body; the original contract, defaults and validation stay in place. ui.Resolve returns the definition that pages use.Rendering a callPropsChildren slotNamed slotsui.Definition.Call(props, slots)Defaults → ValidateBody.templ → _templ.goHTMLescaped textui.RendererLibraries and overridesLibrary appCard.Export(1)Registry"cards:card"Page factoryui.Resolve(reg, Card)project app onlyCard.Override(1)exportreplaces the body only;contract, version,defaults, validation stay

#Ask your agent

Text
Read https://cicadel.fauzaanu.com/en/0.2.8/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.

APIWhat 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.NoSlotsDeclares 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.

HookWhen it runsUse it for
DefaultsFirst, on a local copy of the props.Filling in values.
ValidateBefore 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 source contract rejects:

  • inline script and CSS templates
  • script and style elements (use ui.Script and ui.JSONData)
  • event-handler attributes and dynamic attribute names
  • static style="…" attributes (use a class, or style={ ui.Style(...) })
  • math, iframe, object, embed and obsolete raw-text elements (use ui.Embed for frames)
  • SVG outside the inline SVG policy

Link static stylesheets explicitly. The next section covers each supported alternative.

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

#SVG, styles, scripts and embeds

These are the supported ways to draw vectors, set computed styles, load page scripts, pass data to them and embed third-party players. Each keeps contextual escaping and works under settings.DefaultContentSecurityPolicy, with the two policy settings named below.

#Inline SVG and icons

Write vector drawing directly in .templ markup:

templ
templ Progress(done bool, label string) {
    <svg viewBox="0 0 24 24" class="size-5" aria-label={ label }>
        <title>{ label }</title>
        if done {
            <path d="M4 12l5 5L20 6" fill="none" stroke="currentColor" stroke-width="2"></path>
        }
    </svg>
}

Inside <svg>, the compiler admits:

  • Elements: g, path, circle, ellipse, line, polyline, polygon, rect, title, desc, defs, symbol, use, linearGradient, radialGradient, stop, clipPath, mask, pattern, marker, text, tspan.
  • Attributes: geometry, paint and stroke presentation attributes, id, class, role, viewBox, aria-*, data-*, and the SVG/XLink xmlns. Expressions are escaped like any attribute.
  • Children: text, { expressions }, if, for and switch.

It rejects scripts, style elements and attributes, foreignObject, links (a), image, animation (animate, set), event handlers, spread attributes and component calls inside the drawing. href is allowed only as a literal same-document reference on use (href="#id"). Paint values may reference only the document (fill="url(#gradient)").

For icon files, load a directory once at startup. Each file is checked against the same policy, so a bad file fails startup instead of rendering:

Go
//go:embed icons/*.svg
var iconFiles embed.FS

var Icons = must(ui.LoadIcons(iconFiles, "icons"))
templ
@icons.Icon("chevron-down", "size-4 text-slate-500")       // decorative: aria-hidden
@icons.Labeled("warning", "size-4", "Payment overdue")     // role="img" aria-label

Comments, XML declarations and namespace attributes are dropped. An unknown icon name fails the render with a UI_ICON diagnostic. Files are limited to ui.MaxIconBytes (64 KiB).

#Computed styles

Static styles belong in classes. For values computed per request, such as a progress width, build a typed style:

templ
<div class="h-2 rounded bg-sky-600" style={ ui.Style(ui.Prop("width", ui.Percent(p.Done))) }></div>
<div class="w-(--progress)" style={ ui.Style(ui.Prop("--progress", ui.Percent(p.Done))) }></div>
  • Values are typed: ui.Px, ui.Rem, ui.Em, ui.Percent, ui.Vw, ui.Vh, ui.Number, ui.Color (hex, currentColor, transparent) and ui.ImageURL (checked with ui.URL). NaN and infinities are rejected.
  • Standard properties are allowlisted by value type: sizes, spacing and positions take lengths; opacity, z-index, order and flex factors take numbers; colors take ui.Color; background-image takes ui.ImageURL. Custom properties (--name) take any typed value.
  • An unknown property, a wrong value type or a repeated property fails the render with a UI_STYLE diagnostic.

Browsers apply style attributes only when the policy allows them. Set Settings.StyleAttributes (or CICADEL_STYLE_ATTRIBUTES=true), which adds style-src-attr 'unsafe-inline'. See secure headers.

A plain string expression (style={ value }) is still sanitized by templ, but prefer ui.Style so invalid values fail loudly.

#Page scripts and data islands

Cicadel never emits inline script code. Load external scripts and pass server data as JSON:

templ
@ui.JSONData("chart-data", p.Points)
@ui.Script(ui.ScriptProps{App: "reports", Path: "js/chart.js", Module: true})
JavaScript
// reports/static/js/chart.js
const points = JSON.parse(document.getElementById("chart-data").textContent);
  • ui.Script takes Src (checked with ui.URL) or a static App and Path, resolved to the hashed URL with static.URL. It supports Module, Defer, Async, Integrity (sha256-/sha384-/sha512-) and CrossOrigin.
  • ui.JSONData writes <script type="application/json" id="…">. Browsers do not execute it, so the default policy allows it. The JSON escapes <, >, &, U+2028 and U+2029, so no value can close the element. The id must start with a letter and contain only letters, digits, - and _.
  • Attach behavior from the script with data-* attributes and addEventListener, not inline handlers.

#Third-party embeds

ui.Embed renders an iframe for an allow-listed origin, such as a video player:

Go
// project/settings/settings.go
s.FrameSources = []string{"https://www.youtube-nocookie.com"}
templ
@ui.Embed(ui.EmbedProps{
    Src:             "https://www.youtube-nocookie.com/embed/" + p.VideoID,
    Title:           p.Title,
    Class:           "aspect-video w-full",
    Allow:           []string{"encrypted-media", "picture-in-picture"},
    AllowFullscreen: true,
})
  • Settings.FrameSources is the single allow-list. It becomes the policy’s frame-src, and the application puts it in every request context (and Application.Context) for ui.Embed.
  • The render fails with a UI_EMBED diagnostic when the source origin is not listed, the URL is not absolute http(s), it carries credentials, or Title is empty. Nothing is written for a rejected frame.
  • Frames are loading="lazy" unless Eager is set, and always use referrerpolicy="strict-origin-when-cross-origin". Allow takes permissions-policy feature names. Sandbox, when non-nil, takes allow-scripts, allow-same-origin, allow-popups, allow-forms, allow-presentation and allow-popups-to-escape-sandbox.
  • Outside an application (unit tests), use ui.WithFrameSources(ctx, origins).

#Reverse URLs in components

The application puts its router in every request context, in commands and Start hooks, and in Application.Context. Reverse a named route from any component:

templ
<a href={ urls.Reverse(ctx, "blog:post", "slug", p.Slug) }>{ p.Title }</a>
  • Parameters are name, value pairs. An unknown name, a missing, extra or repeated parameter, an unsafe value (/, .., control characters) or a context without a router returns an error, and templ fails the render with the template location. Values are path-escaped.
  • urls.Current(ctx) is the name of the route serving the request, such as "blog:post". urls.Namespace(ctx) is its namespace ("blog"). Both are "" in global middleware that runs before routing, for unmatched requests and in background contexts. Use them to mark the active navigation item.
  • Handlers can call the same functions with r.Context().

#Render a response

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

MethodBehavior
Renderer.RenderReturns bytes only after a successful render.
Renderer.HTMLRenders, 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

LimitDefaultOverride
Render depth64 framesRenderer{MaxDepth: ...}
Output size4 MiBRenderer{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.
  • 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.

RuleWhy
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 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:

Terminal
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

CommandDoesDoes not
ui generateParses 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 buildGenerates markup and builds configured Tailwind CSS.—
ui checkDetects 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 checkConstructs 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.
  • 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.

FieldMeaning
codeStable diagnostic ID.
messageWhat went wrong.
file, line, columnSource position. One-based.
stackComponent stack.

Stable IDs, by when they happen:

StageCodeMeaning
CompileUI_PARSEThe .templ file could not be parsed.
CompileUI_SYNTAXA syntax error at a reported line and column.
CompileUI_GENERATEGo generation from the template failed.
CompileUI_TYPEThe generated Go does not type-check.
CompileUI_BUILDThe package containing components does not build.
CheckUI_STALEGenerated output is missing or out of date. Run cicadel ui generate.
CheckUI_ORPHANA generated file has no .templ source. Remove it.
RegistryUI_NAMESPACEAn exported component name is invalid in its namespace.
RegistryUI_COLLISIONTwo components or overrides share a name.
RegistryUI_UNKNOWNAn override targets a component that does not exist.
RegistryUI_CONTRACTAn override or resolve does not match the original contract.
RenderUI_ATTRIBUTEAn attribute name is invalid or not lowercase.
RenderUI_URLA URL is unsafe or contains control characters.
RenderUI_LIMITOutput exceeded the render byte limit.
RenderUI_RENDERA component failed while rendering.
RenderUI_WRITEWriting the response failed after output started.

Tailwind has its own UI_CSS_* codes; see the Tailwind guide.

  • 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

ComponentProvides
ui/layout.PageA 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.FieldAssociated labels, bound values, required and maxlength, password/text/textarea variants, and error-only aria-invalid and aria-describedby.
ui/form.CSRFA 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.

Type to search this version.