# Creating a New Component
A component is a **reusable stamp**. You design the stamp once (the `.htmx` template + `.htmx.cs` class), and then press it anywhere you need that piece of UI — on multiple pages, inside other components, even multiple times on the same page.
Components are identical in structure to pages, with two key differences:
1. They live in `Templates/Components/` instead of `Templates/`
2. They are never responsible for HTTP routing — they just render HTML
---
## What you want to achieve
By the end of this guide you will be able to build any reusable UI piece — a styled label, a card, a form field, or a wrapper that holds other components — and drop it anywhere on a page.
---
## The three patterns
All components fit one of three shapes. Pick the one that matches what you are building.
---
### Pattern A — A simple label or display element
Use this when the component just renders a styled string. It is the simplest case.
**Goal:** a coloured status badge you can reuse in tables, cards, and headers.
```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;
public Badge(string label, string variant = "default")
{
_labelData = label.ToUtf8Bytes();
var variantClasses = 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 {variantClasses}"
.ToUtf8Bytes();
}
protected override void RenderLabel(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_labelData);
protected override void RenderClasses(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_classesData);
}
```
The key principle: **all computation happens in the constructor**. By the time `RenderLabel` is called during a request, it is just writing pre-computed bytes — no string formatting, no allocations.
---
### Pattern B — A container with optional sections
Use this when parts of the component are optional — for example a card that shows a header only when a title is provided.
**Goal:** a card that always shows its body, but optionally shows a header and a footer.
```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();
// Build header HTML in the constructor. If there's no title/description,
// store an empty array — writing empty bytes is a no-op.
_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 — A wrapper that holds other components
Use this when a slot should be filled by another component rather than a string.
**Goal:** a tooltip wrapper — the trigger is any component, and the tooltip text floats above it on hover.
```html
$$Trigger$$
$$Text$$
```
```csharp
// Templates/Components/Tooltip.htmx.cs
namespace Htmx.ApiDemo.Templates.Components;
public sealed class Tooltip : TooltipBase
{
private readonly IHtmxComponent _trigger;
private readonly byte[] _textData;
public Tooltip(string text, IHtmxComponent trigger)
{
_textData = text.ToUtf8Bytes();
_trigger = trigger;
}
protected override void RenderText(HtmxRenderContext ctx)
=> ctx.Writer.WriteUtf8(_textData);
// ctx.Next() increments the nesting depth counter.
// The runtime throws if nesting exceeds 512 levels — this is the guard against infinite loops.
protected override void RenderTrigger(HtmxRenderContext ctx)
=> _trigger.Render(ctx.Next());
}
```
---
## Using a component inside a page
Once you have a component, use it from a page's code-behind. The page stores the component as a field and delegates `Render` from its slot override:
```csharp
// MyPage.htmx.cs
public sealed class MyPage : MyPageBase
{
private readonly byte[] _headingData;
private readonly IHtmxComponent _statusBadge;
public MyPage(string heading, string status)
{
_headingData = heading.ToUtf8Bytes();
_statusBadge = new Badge(status, variant: "secondary");
}
protected override void RenderHeading(HtmxRenderContext ctx)
=> ctx.Writer.WriteUtf8(_headingData);
protected override void RenderStatusBadge(HtmxRenderContext ctx)
=> _statusBadge.Render(ctx.Next());
}
```
Template:
```html
$$Heading$$
$$StatusBadge$$
```
---
## A note on HTML safety
`WriteUtf8` writes raw bytes directly to the HTTP response. It does **not** HTML-encode anything.
- Strings you write in the constructor that come from your own code are fine — you control them.
- Any value that comes from user input (a form field, a database value, a query parameter) **must be HTML-encoded before calling `ToUtf8Bytes()`**:
```csharp
// Safe — encodes characters like < > " &
_nameData = System.Web.HttpUtility.HtmlEncode(userInput).ToUtf8Bytes();
```
Skipping this step is a cross-site scripting (XSS) vulnerability.
---
## File location and namespace
| File location | C# 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. Always keep components in `Templates/Components/`.
---
## Checklist
- [ ] `.htmx` template created in `Templates/Components/` with `$$PascalCase$$` slots
- [ ] `.htmx.cs` class inherits the generated `XxxBase` class
- [ ] All `RenderXxx` overrides implemented
- [ ] Computation (string building, class selection) done in the constructor
- [ ] User-provided strings HTML-encoded before `ToUtf8Bytes()`
- [ ] Sub-component `Render` calls use `ctx.Next()` not bare `ctx`
// 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