The code and data behind xeiaso.net
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(skills/templ-components): split deep guidance into resources

Convert the skill to progressive disclosure and move reusable patterns, API guidance, and testing notes into dedicated resource files.

Assisted-by: openai/gpt-5.3-codex via OpenCode
Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso ab319de9 f6a51eea

+162 -485
+34 -485
.claude/skills/templ-components/SKILL.md
··· 5 5 6 6 # Templ Components 7 7 8 - ## Overview 8 + Use progressive disclosure: solve with Level 1 first, then pull deeper guidance only if complexity requires it. 9 9 10 - Build reusable, type-safe UI components with templ. Components accept strongly-typed props and can be composed together to create complex UIs. 10 + ## Level 1: Component Checklist 11 11 12 - ## When to Use This Skill 13 - 14 - Use when: 15 - 16 - - Creating reusable UI components 17 - - Building component libraries 18 - - User mentions "button", "card", "form", "modal" components 19 - - Designing component APIs 20 - - Working on design systems 21 - 22 - ## Core Component Patterns 23 - 24 - ### Basic Component 25 - 26 - ```templ 27 - package components 28 - 29 - templ Button(text string) { 30 - <button class="btn"> 31 - { text } 32 - </button> 33 - } 34 - ``` 35 - 36 - ### Component with Multiple Props 37 - 38 - ```templ 39 - templ Button(text string, variant string, disabled bool) { 40 - <button 41 - class={ "btn btn-" + variant } 42 - disabled?={ disabled } 43 - > 44 - { text } 45 - </button> 46 - } 47 - ``` 48 - 49 - Usage: 50 - 51 - ```go 52 - components.Button("Submit", "primary", false) 53 - ``` 54 - 55 - ### Component with Children 56 - 57 - ```templ 58 - templ Card(title string) { 59 - <div class="card"> 60 - <div class="card-header"> 61 - <h3>{ title }</h3> 62 - </div> 63 - <div class="card-body"> 64 - { children... } 65 - </div> 66 - </div> 67 - } 68 - ``` 69 - 70 - Usage: 71 - 72 - ```templ 73 - @Card("User Profile") { 74 - <p>Name: John Doe</p> 75 - <p>Email: john@example.com</p> 76 - } 77 - ``` 12 + Use this skill for reusable templ UI components. 78 13 79 - ### Component with Struct Props 14 + 1. Define a small, single-purpose component. 15 + 2. Prefer typed props (structs for complex APIs). 16 + 3. Support composition with `{ children... }`. 17 + 4. Keep variants explicit and predictable. 18 + 5. Extract shared layout wrappers instead of duplicating markup. 80 19 81 20 ```templ 82 - package components 83 - 84 21 type ButtonProps struct { 85 - Text string 22 + Label string 86 23 Variant string 87 24 Disabled bool 88 - OnClick string 89 25 } 90 26 91 27 templ Button(props ButtonProps) { 92 - <button 93 - class={ "btn btn-" + props.Variant } 94 - disabled?={ props.Disabled } 95 - onclick={ props.OnClick } 96 - > 97 - { props.Text } 28 + <button class={ "btn btn-" + props.Variant } disabled?={ props.Disabled }> 29 + { props.Label } 98 30 </button> 99 31 } 100 32 ``` 101 33 102 - ## Common Components 103 - 104 - ### Button Variants 105 - 106 - ```templ 107 - templ PrimaryButton(text string) { 108 - <button class="btn btn-primary">{ text }</button> 109 - } 110 - 111 - templ SecondaryButton(text string) { 112 - <button class="btn btn-secondary">{ text }</button> 113 - } 114 - 115 - templ DangerButton(text string, onclick string) { 116 - <button class="btn btn-danger" onclick={ onclick }> 117 - { text } 118 - </button> 119 - } 120 - ``` 121 - 122 - ### Card Component 123 - 124 - ```templ 125 - templ Card(title string, footer string) { 126 - <div class="card"> 127 - if title != "" { 128 - <div class="card-header"> 129 - <h3>{ title }</h3> 130 - </div> 131 - } 132 - <div class="card-body"> 133 - { children... } 134 - </div> 135 - if footer != "" { 136 - <div class="card-footer"> 137 - { footer } 138 - </div> 139 - } 140 - </div> 141 - } 142 - ``` 143 - 144 - ### List Component 145 - 146 - ```templ 147 - type ListItem struct { 148 - ID string 149 - Text string 150 - } 151 - 152 - templ List(items []ListItem) { 153 - <ul class="list"> 154 - for _, item := range items { 155 - <li data-id={ item.ID }> 156 - { item.Text } 157 - </li> 158 - } 159 - </ul> 160 - } 161 - ``` 162 - 163 - ### Modal Component 164 - 165 - ```templ 166 - templ Modal(id string, title string, isOpen bool) { 167 - <div 168 - class={ "modal", templ.KV("modal-open", isOpen) } 169 - id={ id } 170 - > 171 - <div class="modal-backdrop"></div> 172 - <div class="modal-content"> 173 - <div class="modal-header"> 174 - <h2>{ title }</h2> 175 - <button class="modal-close">&times;</button> 176 - </div> 177 - <div class="modal-body"> 178 - { children... } 179 - </div> 180 - </div> 181 - </div> 182 - } 183 - ``` 184 - 185 - ### Form Components 186 - 187 - ```templ 188 - templ Input(name string, label string, value string) { 189 - <div class="form-group"> 190 - <label for={ name }>{ label }</label> 191 - <input 192 - type="text" 193 - id={ name } 194 - name={ name } 195 - value={ value } 196 - class="form-control" 197 - /> 198 - </div> 199 - } 200 - 201 - templ TextArea(name string, label string, rows int) { 202 - <div class="form-group"> 203 - <label for={ name }>{ label }</label> 204 - <textarea 205 - id={ name } 206 - name={ name } 207 - rows={ strconv.Itoa(rows) } 208 - class="form-control" 209 - > 210 - { children... } 211 - </textarea> 212 - </div> 213 - } 34 + ## Level 2: API Design Rules 214 35 215 - templ Select(name string, label string, options []string) { 216 - <div class="form-group"> 217 - <label for={ name }>{ label }</label> 218 - <select id={ name } name={ name } class="form-control"> 219 - for _, option := range options { 220 - <option value={ option }>{ option }</option> 221 - } 222 - </select> 223 - </div> 224 - } 225 - ``` 36 + - **Small surface area:** avoid giant prop lists. 37 + - **Typed options:** enums/constants beat free-form strings for critical variants. 38 + - **Composition first:** prefer wrappers (`Card`, `Modal`, `Layout`) over monolith components. 39 + - **No hidden side effects:** components render; handlers/loaders do data work. 40 + - **Stable contracts:** avoid breaking prop shape without migration. 226 41 227 - ## Layout Components 228 - 229 - ### Container 42 + ## Level 3: Reusable Patterns 230 43 231 44 ```templ 232 - templ Container(fluid bool) { 233 - <div class={ templ.KV("container", !fluid), templ.KV("container-fluid", fluid) }> 234 - { children... } 235 - </div> 236 - } 237 - ``` 238 - 239 - ### Grid 240 - 241 - ```templ 242 - templ Row() { 243 - <div class="row"> 244 - { children... } 245 - </div> 246 - } 247 - 248 - templ Col(size int) { 249 - <div class={ "col-" + strconv.Itoa(size) }> 250 - { children... } 251 - </div> 252 - } 253 - ``` 254 - 255 - Usage: 256 - 257 - ```templ 258 - @Container(false) { 259 - @Row() { 260 - @Col(6) { 261 - <p>Left column</p> 262 - } 263 - @Col(6) { 264 - <p>Right column</p> 265 - } 266 - } 267 - } 268 - ``` 269 - 270 - ### Navigation 271 - 272 - ```templ 273 - type NavItem struct { 274 - Text string 275 - Href string 276 - Active bool 277 - } 278 - 279 - templ Nav(items []NavItem) { 280 - <nav class="navbar"> 281 - <ul class="nav"> 282 - for _, item := range items { 283 - <li class={ templ.KV("nav-item-active", item.Active) }> 284 - <a href={ templ.URL(item.Href) }> 285 - { item.Text } 286 - </a> 287 - </li> 288 - } 289 - </ul> 290 - </nav> 291 - } 292 - ``` 293 - 294 - ## Composition Patterns 295 - 296 - ### Slots Pattern 297 - 298 - ```templ 299 - templ Layout(title string, headerContent templ.Component, footerContent templ.Component) { 300 - <!DOCTYPE html> 301 - <html> 302 - <head> 303 - <title>{ title }</title> 304 - </head> 305 - <body> 306 - <header> 307 - @headerContent 308 - </header> 309 - <main> 310 - { children... } 311 - </main> 312 - <footer> 313 - @footerContent 314 - </footer> 315 - </body> 316 - </html> 317 - } 318 - ``` 319 - 320 - ### Render Props Pattern 321 - 322 - ```templ 323 - templ DataTable(headers []string, renderRow func(int) templ.Component) { 324 - <table> 325 - <thead> 326 - <tr> 327 - for _, header := range headers { 328 - <th>{ header }</th> 329 - } 330 - </tr> 331 - </thead> 332 - <tbody> 333 - for i := 0; i < 10; i++ { 334 - @renderRow(i) 335 - } 336 - </tbody> 337 - </table> 338 - } 339 - ``` 340 - 341 - ### Wrapper Components 342 - 343 - ```templ 344 - templ WithLoading(isLoading bool) { 345 - if isLoading { 346 - <div class="spinner">Loading...</div> 347 - } else { 348 - { children... } 349 - } 45 + templ Card(title string) { 46 + <article class="card"> 47 + <header><h3>{ title }</h3></header> 48 + <div class="card-body">{ children... }</div> 49 + </article> 350 50 } 351 51 352 52 templ WithError(err error) { 353 53 if err != nil { 354 - <div class="alert alert-error"> 355 - { err.Error() } 356 - </div> 54 + <p class="error">{ err.Error() }</p> 357 55 } else { 358 56 { children... } 359 57 } 360 58 } 361 59 ``` 362 60 363 - ## Best Practices 61 + ## Escalate to Other Skills 364 62 365 - ### 1. Single Responsibility 63 + - Need syntax details: use `templ-syntax`. 64 + - Need HTTP routing/rendering: use `templ-http`. 65 + - Need partial updates/interactions: use `templ-htmx`. 366 66 367 - ```templ 368 - // ✅ Good: One purpose 369 - templ Avatar(src string, alt string) { 370 - <img src={ src } alt={ alt } class="avatar" /> 371 - } 67 + ## References 372 68 373 - // ❌ Bad: Too many responsibilities 374 - templ UserSection(user User, posts []Post, comments []Comment) { 375 - // Too much in one component 376 - } 377 - ``` 378 - 379 - ### 2. Type-Safe Props 380 - 381 - ```templ 382 - // ✅ Good: Strongly typed 383 - type ButtonProps struct { 384 - Text string 385 - Variant ButtonVariant 386 - } 387 - 388 - type ButtonVariant string 389 - 390 - const ( 391 - Primary ButtonVariant = "primary" 392 - Secondary ButtonVariant = "secondary" 393 - ) 394 - 395 - templ Button(props ButtonProps) { 396 - <button class={ "btn btn-" + string(props.Variant) }> 397 - { props.Text } 398 - </button> 399 - } 400 - ``` 401 - 402 - ### 3. Composition Over Complexity 403 - 404 - ```templ 405 - // ✅ Good: Compose small components 406 - templ UserCard(user User) { 407 - @Card(user.Name) { 408 - @Avatar(user.AvatarURL, user.Name) 409 - @UserInfo(user) 410 - @UserActions(user.ID) 411 - } 412 - } 413 - 414 - // Each sub-component is simple and reusable 415 - ``` 416 - 417 - ### 4. Conditional Rendering 418 - 419 - ```templ 420 - // ✅ Good: Clear conditions 421 - templ Message(text string, isError bool) { 422 - if isError { 423 - <div class="alert-error">{ text }</div> 424 - } else { 425 - <div class="alert-info">{ text }</div> 426 - } 427 - } 428 - ``` 429 - 430 - ### 5. Default Props 431 - 432 - ```go 433 - // In Go code 434 - type ButtonProps struct { 435 - Text string 436 - Variant string 437 - Disabled bool 438 - } 439 - 440 - func NewButton(text string) ButtonProps { 441 - return ButtonProps{ 442 - Text: text, 443 - Variant: "primary", 444 - Disabled: false, 445 - } 446 - } 447 - ``` 448 - 449 - ```templ 450 - templ Button(props ButtonProps) { 451 - <button 452 - class={ "btn btn-" + props.Variant } 453 - disabled?={ props.Disabled } 454 - > 455 - { props.Text } 456 - </button> 457 - } 458 - ``` 459 - 460 - ### Package Organization 461 - 462 - ```templ 463 - // components/ui/button.templ 464 - package ui 465 - 466 - templ Button(text string) { 467 - <button class="btn">{ text }</button> 468 - } 469 - ``` 470 - 471 - ```templ 472 - // components/layout/container.templ 473 - package layout 474 - 475 - templ Container() { 476 - <div class="container"> 477 - { children... } 478 - </div> 479 - } 480 - ``` 481 - 482 - Usage: 483 - 484 - ```go 485 - import ( 486 - "myapp/components/ui" 487 - "myapp/components/layout" 488 - ) 489 - 490 - layout.Container() { 491 - ui.Button("Click me") 492 - } 493 - ``` 494 - 495 - ## Testing Components 496 - 497 - ```go 498 - func TestButton(t *testing.T) { 499 - var buf bytes.Buffer 500 - props := ButtonProps{ 501 - Text: "Submit", 502 - Variant: "primary", 503 - } 504 - 505 - err := Button(props).Render(context.Background(), &buf) 506 - if err != nil { 507 - t.Fatal(err) 508 - } 509 - 510 - html := buf.String() 511 - if !strings.Contains(html, "Submit") { 512 - t.Error("Button text not found") 513 - } 514 - if !strings.Contains(html, "btn-primary") { 515 - t.Error("Primary class not found") 516 - } 517 - } 518 - ``` 519 - 520 - ## Next Steps 521 - 522 - - **Connect to server** → Use `templ-http` skill 523 - - **Add interactivity** → Use `templ-htmx` skill 524 - - **Style components** → Use `templ-css` skill 69 + - Foundations: `resources/foundations.md` 70 + - Patterns: `resources/patterns.md` 71 + - Testing and API notes: `resources/testing-and-api.md` 72 + - templ component guide: https://templ.guide/component-composition/ 73 + - templ API docs: https://pkg.go.dev/github.com/a-h/templ
+39
.claude/skills/templ-components/resources/foundations.md
··· 1 + # Templ Components Foundations 2 + 3 + Use this for component API design and composition rules. 4 + 5 + templ components compile to Go functions returning `templ.Component`. 6 + 7 + ## Typed Props 8 + 9 + ```templ 10 + type ButtonProps struct { 11 + Text string 12 + Variant string 13 + Disabled bool 14 + } 15 + 16 + templ Button(props ButtonProps) { 17 + <button class={ "btn btn-" + props.Variant } disabled?={ props.Disabled }> 18 + { props.Text } 19 + </button> 20 + } 21 + ``` 22 + 23 + ## Core Rules 24 + 25 + - Keep components single-purpose. 26 + - Prefer struct props once call sites exceed 2-3 arguments. 27 + - Support composition with `{ children... }` for wrapper components. 28 + - Avoid embedding fetch/mutation logic in templ components. 29 + - Prefer idempotent render behavior: pass data in, render out. 30 + - Export reusable components with capitalized names for cross-package use. 31 + 32 + ## Code-Only Components 33 + 34 + You can implement components in Go with `templ.ComponentFunc`, but then escaping and HTML safety are your responsibility. 35 + 36 + ## Sources 37 + 38 + - https://templ.guide/core-concepts/components 39 + - https://templ.guide/syntax-and-usage/template-composition
+45
.claude/skills/templ-components/resources/patterns.md
··· 1 + # Templ Components Patterns 2 + 3 + ## Wrapper Pattern 4 + 5 + ```templ 6 + templ Card(title string) { 7 + <article class="card"> 8 + <header><h3>{ title }</h3></header> 9 + <section>{ children... }</section> 10 + </article> 11 + } 12 + ``` 13 + 14 + ## Guard/Decorator Pattern 15 + 16 + ```templ 17 + templ WithError(err error) { 18 + if err != nil { 19 + <div class="alert alert-error">{ err.Error() }</div> 20 + } else { 21 + { children... } 22 + } 23 + } 24 + ``` 25 + 26 + ## Layout Composition 27 + 28 + - Build pages by nesting small components. 29 + - Prefer `Layout -> Section -> Leaf` composition over one large component. 30 + - Pass render fragments/components when you need slot-like APIs. 31 + 32 + ## Slot API Choices 33 + 34 + - Use `{ children... }` when call-site readability matters most. 35 + - Use explicit `templ.Component` parameters when slots need names (`header`, `footer`, `actions`). 36 + - Use `templ.Join(...)` in Go to stitch optional components in a predictable order. 37 + 38 + ## View Model Pattern 39 + 40 + When domain models do not match display needs, shape a view model first and pass that into components. This keeps templates simple and easier to test. 41 + 42 + ## Sources 43 + 44 + - https://templ.guide/syntax-and-usage/template-composition 45 + - https://templ.guide/core-concepts/view-models
+44
.claude/skills/templ-components/resources/testing-and-api.md
··· 1 + # Templ Components Testing and API Notes 2 + 3 + ## API Stability 4 + 5 + - Use constants/enums for variant-like props. 6 + - Avoid stringly-typed values for high-impact options. 7 + - Keep prop contracts stable across releases. 8 + 9 + ## Basic Render Test 10 + 11 + ```go 12 + func TestButton(t *testing.T) { 13 + var buf bytes.Buffer 14 + err := Button(ButtonProps{Text: "Submit", Variant: "primary"}).Render(context.Background(), &buf) 15 + if err != nil { 16 + t.Fatal(err) 17 + } 18 + html := buf.String() 19 + if !strings.Contains(html, "Submit") { 20 + t.Fatal("missing button text") 21 + } 22 + } 23 + ``` 24 + 25 + ## Testing Styles 26 + 27 + - Expectation tests: parse output and assert specific structure/content. 28 + - Snapshot tests: compare rendered HTML against golden/snapshot output. 29 + - Use `data-testid` markers for resilient selectors when parsing with `goquery`. 30 + 31 + ## Test Focus 32 + 33 + - Render success/failure paths. 34 + - Presence of critical labels/attributes/classes. 35 + - Conditional rendering branches. 36 + 37 + ## Render Error Behavior 38 + 39 + Rendering can write partial output before returning an error. For all-or-nothing behavior in tests or handlers, render to a buffer first and only write after success. 40 + 41 + ## Sources 42 + 43 + - https://templ.guide/core-concepts/testing 44 + - https://templ.guide/core-concepts/components