# 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