HTML Element component authoring
HTML Element Component Authoring
Summary
This feature enables HTML Element Component authoring in templ with inline component attribute support
Overview
The implementation extends templ's generator to support HTML element component syntax where components are invoked using HTML element syntax:
templ Container(title string, child templ.Component) {
<div class="container">
<h1>{ title }</h1>
<section>{ child }</section>
{children...}
</div>
}
templ Page() {
<Container title="About me" child={ <span>I love templ</span> }>
<p>This is a templ page with a custom component.</p>
</Container>
}
Parser
The parse is extended to recognize HTML Element Component, which must be Title cased. Components from external packages are also handled. If the component is a struct method, it can be lower case. E.g. pkg.Var.Field.Page() or var.field1.field2.page() etc
Key Types Added:
ElementComponent- Main AST node representing component syntax with name, attributes, children, and positioning infoKeyedAttributeinterface - Enables attribute key access for mapping to Go function parametersInlineComponentAttribute- Handles complex attribute values containing templ blocks (e.g.,header={<div>content</div>})AttributeComment- Supports comments within element attributes (not related to this PR, but it has been bugging me for a while)- Visitor methods -
VisitElementComponentandVisitInlineComponentAttributefor AST traversal
Generator
-
writeElementComponent(generator.go:907-914): Main entry point that handles both self-closing and block element components. -
writeElementComponentFunctionCall(generator.go:1324-1376): Generates the actual Go function call by:- Looking up the component signature in
g.componentSigs - Calling
writeElementComponentAttrVarsto handle attributes - Writing the function call with proper arguments
- Looking up the component signature in
-
Attribute Handling (generator.go:970-1087): The
writeElementComponentAttrVarsfunction:- Reorders attributes to match function parameter order
- Handles different attribute types (constants, expressions, boolean, etc.)
- Supports rest parameters for extra attributes via
templ.Attributer
Symbol Resolution
- Generator receives working directory context
- Implemented symbolresolver.go to resolve component signatures from go files from local package and go or templ files in external packages
- Template Signature Resolution: Added templsignatureresolver.go for parsing component function signatures
- Component Element processor maps attribute names to parameters using resolved signatures
- Struct that implements
templ.Componentmaps attribute names to parameters using field names
See generator.go:2268-2342: The collectAndResolveComponents function:
- Finds all unique element components in the template
- Resolves their Go function signatures
- Supports both local and imported components
- Stores signatures for code generation
LSP Integration (cmd/templ/lspcmd/)
-
Added some LSP test for the new HTML Element Component syntax
-
Added a new test mode for lspcmd to easily test lsp errors on the command line.
This could turn into a bug report tool for users to report issues with the templ LSP server.
LSP implementation has lots of room for improvement. I don't recommend finishing it in this PR
Tests
Added ./generator/test-element-component/. It contains most of the new syntax that this PR introduces. It's created as a separate module to test the package resolution works as expected. This won't be
covered by running go test ./... in the root of the templ repository. Therefore a new section in REEADME.md is added, called test-element-component.
Also added an annotated templ source file to test expect-diagnostics. Check ./cmd/templ/lspcmd/.testdata/README.md for details. Examples in the same folder
Summary
The implementation maintains templ's core philosophy by sticking to HTML component authoring patterns.
Fixes #1181
- [x] Element Component Parser
- [x] Element Component Attribute handling, should support most of html Element attribute types
- [x] Anonymous component block with primitive type support
- [x] Rest attribute handling
- ~[ ] LSP attribute name range jump to definition~ Will follow up with separate PR
- [x] Documentation
caveat
- when the argument is a
templ.Component, you can only use@varsyntax with in the templ block. E.g.
UPDATE: this is now support for local blocked variablestempl Container(title string, child templ.Component) { <div class="container"> <h1>{ title }</h1> <section> @child </section> {children...} </div> }
Documentation Preview
@a-h & @joerdav , I marked this as ready for review because I believe the first phase is complete:
The PR currently allows us to write a HTML tag in place of a @Component syntax, provided Component is upper cased.
There is no check if the name of the parameter vs the attribute name: it's totally order dependent. E.g.
<Comp x={v1} y={v2} z={v1}
becomes
@Comp(v1, v2, v3)
without order check.
In addition, this PR also enabled the LSP functionalities for hover and jump to definition. This is only on the Component name, not on the attribute obviously.
As discussed in #1181 , I plan to take this in steps (modified slightly):
1: Add JSX component parsing -- done 2: Add the "compile" step to query callee signatures, to enable our of order attribute vs function/method argument 3. Anonymous component block discussed in #1150
Let me know what you think.
Ok since this is going so well, I decided to complete the rest functionalities. The symbols resolver, the anonymous templ block
Nice work! I've raised a few points to consider.
I think it will be hard work to deal with all the little details that would make this feature work, so hopefully you've got time to see this through. 😁
Hi @a-h , thanks for the review. Agreed with most of the points, the attribute handling part is still being worked on.
I think I can complete this. 1) I need this and want this. 2) Claude is being very helpful in understanding the code base and writes most of the code actually.
I've changed my objectives a bit, to include documentation. So that you guys can review the whole thing.
Thanks. An explanation of the thinking on the software architecture would be useful.
One thought I had was how "extra" attributes (stuff that isn't an argument or field) should be handled.
By default, if you add an attribute that doesn't exist as a field or function arg, we should display a warning diagnostic.
However, templ has templ.Attributes which can represent any attributes, it works in normal code because the name of the field or argument tells you where the attributes will be used, but I'm not sure what the rules should be here. Would extra attributes be allowed if there's an "Attrs" field or "attrs" argument of type templ.Attributes? I think so. Happy for this to come later, because if you need dynamic attributes, you can just fall back to the @Component syntax.
However, templ has templ.Attributes which can represent any attributes, it works in normal code because the name of the field or argument tells you where the attributes will be used, but I'm not sure what the rules should be here. Would extra attributes be allowed if there's an "Attrs" field or "attrs" argument of type templ.Attributes? I think so. Happy for this to come later, because if you need dynamic attributes, you can just fall back to the https://github.com/component syntax.
Hi @a-h , thanks for the affirmation, I'd like to share more about my thought my process, and discuss with you guys to make sure we're aligned. Last thing I want to do is I implement something, but doesn't go with your design principles.
Majority of the question and reviews you raised is actually explain in my original issue. If we need an executive summery, it would be:
Element component (let's use this name for now) should follow mostly how HTML element in works.
It can have most of the attributes explained in https://templ.guide/syntax-and-usage/attributes. Notably, it should allow (v, err...) values to allow error handling.
Additionally it should allow value={ <div> or (stringable, error) } as anonymous templ component block.
For rest attr grouping, I actually touched on this in the issue discussion's "var arg" section. The idea is: anything that's not in the function/method's regular arguments, and if the last argument of the callee is a templ.Attributer, we'll pass in the rest attributes to it. If there is no attr, I ignore it for now. We can publish a diagnostic later.
I largely skipping the advanced LSP functionalities, only adding ranges for now. Hopefully we can complete the rest later? I want to a lot to the LSP, e.g. attribute name jump to definition and hover etc. However, I think we should defer it to later.
Hopefully I'll write some documentation once I finish the essential features. Oh, I plan to test it on my projects by converting them to the new syntax before completing the PR, to validate my ideas.
Please let me know if you agree.
@a-h , I've completed all the functionalities and responded to all your review comments (thanks again). Can you please review again?
I know there are a lot of clean ups todo. I suggest we focus on the functionalities and compatibility. Once this is stable, we can start other PRs to clean up parts of this and improve the LSP functionalities
@a-h sorry I said this was ready yesterday but I implemented a few more improvements today and addressed all your review comments (I think). Notably:
- pointer fields to value fields
- struct type implements Component
- use existing visitors
Also:
- improved LSP and added some testing facilities
- add docs
I believe it's in good shape to be reviewed.
Sorry this may sounds harsh, but what problem is this solving? I moved away from Vue and Svelte because Templ was simple and felt like native Go (and I love it). Now it’s turning into JSX.
It feels like people keep adding features to solve problems that don’t exist, thinking, “this might help!”
Adding features is easy — the hard part is deciding what not to add. Just like the Go team carefully avoids unnecessary syntax (e.g., rejecting handle).
I’d rather see fewer features that just work than another JSX clone in Go.
There's a post that might be related to this and expresses my stance: The future of htmx
No New Features as a Feature
We are going to be increasingly inclined to not accept new proposed features in the library core.
People shouldn’t feel pressure to upgrade htmx over time unless there are specific bugs that they want fixed, and they should feel comfortable that the htmx that they write in 2025 will look very similar to htmx they write in 2035 and beyond.
The issue linked to this pr explains all the rationale
On Sun, 29 Jun 2025, 18:09 Yami Odymel, @.***> wrote:
YamiOdymel left a comment (a-h/templ#1193) https://github.com/a-h/templ/pull/1193#issuecomment-3016873897
Sorry this may sounds harsh, but what problem is this solving? I moved away from Vue and Svelte because Templ was simple and felt like native Go (and I love it). Now it’s turning into JSX.
It feels like people keep adding features to solve problems that don’t exist, thinking, “this might help!”
Adding features is easy — the hard part is deciding what not to add. Just like the Go team carefully avoids unnecessary syntax (e.g., rejecting handle).
I’d rather see fewer features that just work than another JSX clone in Go.
— Reply to this email directly, view it on GitHub https://github.com/a-h/templ/pull/1193#issuecomment-3016873897, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACYEF7GHXLVYRUDOXKP6BL3GAMU7AVCNFSM6AAAAAB7LNGPKKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTAMJWHA3TGOBZG4 . You are receiving this because you modified the open/close state.Message ID: @.***>
Any clue when this feature will be released?
@rfx77 Thanks for the interest. I'm currently taking a break from this. I have a new branch https://github.com/jackielii/templ/tree/just-symbol-resolver which is my latest efforts. I hope to start my 3rd attempt to combine this PR and the just-symbol-resolver to come up with something fast and support most common features.
If I summerise my two attempts:
1st attempt only considered a simple conversion from the HTML node to the templ node so that generation will work with existing code. The downside is it didn't support a lot of the use cases, and the inlined components which feels like a must have feature. That is snippets like <MyDiv header={ <MyHeader ... /> } .... /> etc.
My 2nd attempt tried a better approach: we need a symbols resolver that collects all the symbols during parsing - that is the go code symbols as well as the templ block symbols. This is akin to the compiling step.
Fun fact, in templ, we only have tokenisation -> parsing -> generation, there is no special treatment to the types whatsoever, all type checking is passed over to go compiler after generation. This is a clever approach which avoided the complex logic and kept the templ generation simple. However, in order to achieve the best generation, we will have to understand the types of all the symbols, this way you can distinguish stuff like: node := "abc" vs node := <abc /> or even node := abc().
In order to resolve all symbols, we're using the golang.org/x/tools/go/packages package to load the symbols and the types. This package is already introduced in in the lazy load contributed by Uber engineers. But our usage of the golang.org/x/tools/go/packages is more comprehensives - it loads all the levels which is the slowest. This is OK during one-off generation. In my testing, it mostly took around 1 second for the tests projects in templ, but still a bit increase from 20ms to >1000ms. This is after I moved the symbols resolver to a global one. This will be a problem for the watch mode which I haven't even touched. I want to make it work then make it fast.
There are more problems in the way to achieve a working solution. In the last couple of month, I thought about this on and off. I question the benefit of this PR - what benefits does it bring; is it worth the extra generation times? Will it be acceptable to most users? I have my rational in the beginning for component composability, i.e. reusable container components. But since then, because of AI coding agent tools happily spewing out pure HTML, I rarely had to re-organise my components to make the reusable. So my motivation waned.
Also I still might complete this, even if the generation is very slow. I think the biggest beneficiaries will be UI libraries like templui etc. This should improve the ergonomics of these kind of libs.
No promises though. I encourage anybody who's interested to give it a go.