# Creating a New Component Components are the reusable building blocks of the UI. They follow the same `.htmx` + `.htmx.cs` pair pattern as pages, but they live in `Templates/Components/`, implement `IHtmxComponent`, and are never responsible for HTTP routing. ## The three component patterns All existing components fall into one of three shapes. Pick the one that fits what you are building. --- ### Pattern A — Simple slot component Use this when every piece of output is a plain string set from outside. ```html $$Label$$ ``` ```csharp // Templates/Components/Badge.htmx.cs namespace Htmx.ApiDemo.Templates.Components; public sealed class Badge : BadgeBase { private readonly byte[] _labelData; private readonly byte[] _classesData; // Compute the final class string once in the constructor, // encode to UTF-8 bytes, never allocate again during render public Badge(string label, string variant = "default") { _labelData = label.ToUtf8Bytes(); var variantClass = variant switch { "secondary" => "bg-secondary text-secondary-foreground", "destructive" => "bg-destructive text-destructive-foreground", "outline" => "border border-border text-foreground", _ => "bg-primary text-primary-foreground", }; _classesData = $"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold {variantClass}" .ToUtf8Bytes(); } protected override void RenderLabel(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_labelData); protected override void RenderClasses(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_classesData); } ``` --- ### Pattern B — Conditionally built sections Use this when parts of the template are optional (e.g. a card header that only renders when a title is provided). Build the HTML string in the constructor and store as bytes; leave the byte array empty `[]` when not needed — `WriteUtf8` on an empty span is a no-op. ```html
$$Header$$
$$Content$$
$$Footer$$
``` ```csharp // Templates/Components/Card.htmx.cs namespace Htmx.ApiDemo.Templates.Components; public sealed class Card : CardBase { private readonly byte[] _extraClassesData; private readonly byte[] _headerData; private readonly byte[] _contentData; private readonly byte[] _footerData; public Card( string content, string title = "", string description = "", string footer = "", string extraClasses = "") { _extraClassesData = extraClasses.ToUtf8Bytes(); _contentData = content.ToUtf8Bytes(); // Header is only rendered when a title or description is supplied _headerData = (string.IsNullOrEmpty(title) && string.IsNullOrEmpty(description)) ? [] : BuildHeader(title, description); _footerData = string.IsNullOrEmpty(footer) ? [] : $"""
{footer}
""".ToUtf8Bytes(); } private static byte[] BuildHeader(string title, string description) { var sb = new System.Text.StringBuilder(); sb.Append("""
"""); if (!string.IsNullOrEmpty(title)) sb.Append($"""

{title}

"""); if (!string.IsNullOrEmpty(description)) sb.Append($"""

{description}

"""); sb.Append("
"); return sb.ToString().ToUtf8Bytes(); } protected override void RenderExtraClasses(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_extraClassesData); protected override void RenderHeader(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_headerData); protected override void RenderContent(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_contentData); protected override void RenderFooter(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_footerData); } ``` --- ### Pattern C — Component slots (embedding other components) Use this when a slot should itself be rendered by another `IHtmxComponent`. Store the sub-component as a property and call `component.Render(context)` from the override. ```html
$$Inner$$
``` ```csharp // Templates/Components/MyWrapper.htmx.cs namespace Htmx.ApiDemo.Templates.Components; public sealed class MyWrapper : MyWrapperBase { private readonly IHtmxComponent _inner; public MyWrapper(IHtmxComponent inner) { _inner = inner; } // Pass context.Next() so the recursion depth counter increments; // the runtime throws if nesting exceeds 512 levels protected override void RenderInner(HtmxRenderContext ctx) => _inner.Render(ctx.Next()); } ``` The depth guard (`context.Next()`) is automatically enforced by the infrastructure generated in `HtmxInfrastructure.g.cs`. You do not need to check it yourself. --- ## Embedding a component in a page Once a component implements `IHtmxComponent`, use it from a page's code-behind by assigning an instance to an `IHtmxComponent` property and delegating `Render`: ```csharp // inside MyPage.htmx.cs public IHtmxComponent MyBadge { get; } public MyPage(...) { MyBadge = new Badge("New", variant: "secondary"); } protected override void RenderMyBadge(HtmxRenderContext ctx) => MyBadge.Render(ctx.Next()); ``` The corresponding slot in `MyPage.htmx`: ```html
Status: $$MyBadge$$
``` --- ## File naming and namespace rules | File location | Generated namespace | |---|---| | `Templates/Components/MyComp.htmx` | `Htmx.ApiDemo.Templates.Components` | | `Templates/MyPage.htmx` | `Htmx.ApiDemo.Templates` | The source generator derives the namespace from the folder path relative to the project root. Keep components in `Templates/Components/` so they land in the right namespace and stay separate from page templates. --- ## HTML user content safety The `WriteUtf8` method writes raw bytes directly to the response. **It does not HTML-encode.** - Static strings you write in the constructor are trusted — you control them. - Any value that comes from user input (e.g. a form field, a database string) **must be HTML-encoded before calling `ToUtf8Bytes()`**. ```csharp // Safe — user-supplied string is encoded first _displayNameData = System.Web.HttpUtility.HtmlEncode(userDisplayName).ToUtf8Bytes(); ``` The existing `MainLayout` constructor demonstrates this for the user initials section. --- ## Checklist - [ ] `MyComp.htmx` created in `Templates/Components/` - [ ] `MyComp.htmx.cs` created with class inheriting `MyCompBase` - [ ] All `$$Slot$$`s have a matching `RenderSlot` override - [ ] User-supplied strings are HTML-encoded before `ToUtf8Bytes()` - [ ] Build once to confirm the compiler catches any missing overrides