# Breadcrumb A navigation trail showing the user's location in the app hierarchy. Items are separated by chevron icons. The last item is always rendered as plain text (current page); earlier items are links. --- ## HTML structure ``` nav[aria-label=Breadcrumb] ol.flex.flex-wrap.items-center.gap-1.5.text-sm.text-muted-foreground li.inline-flex.items-center.gap-1.5 ← one per item a | span ← a = link, span = non-linked or current span[role=presentation, aria-hidden] ← chevron separator (omitted after last item) svg (3.5×3.5, chevron-right) ``` --- ## CSS mechanics | Class | Effect | |---|---| | `text-muted-foreground` | Dimmed color for all non-current items | | `font-normal text-foreground` | Full-opacity color applied to the last (current) item | | `hover:text-foreground transition-colors` | Link hover state | | `flex-wrap` | Items wrap on narrow screens | --- ## Constructor signature ```csharp public Breadcrumb(IEnumerable<(string Label, string Href)> items) ``` | Parameter | Description | |---|---| | `items` | Ordered list of `(Label, Href)` tuples. The last item is always the current page. | Rules: - The **last** item is always non-linked and rendered in full `text-foreground` color, regardless of its `Href` value. - Any **non-last** item with an empty `Href` is rendered as a plain `` rather than a link. --- ## Usage examples ### Simple three-level breadcrumb ```csharp new Breadcrumb(new[] { ("Home", "/"), ("Settings", "/settings"), ("Profile", ""), // current page — href is ignored for the last item }) ``` ### Dynamic breadcrumb from a data path ```csharp // Build items from a category tree var crumbs = categoryPath .Select((cat, i) => (cat.Name, i < categoryPath.Count - 1 ? cat.Url : "")) .ToArray(); new Breadcrumb(crumbs) ``` ### Embedded in a page ```html
$$Breadcrumb$$
``` ```csharp // MyPage.htmx.cs public IHtmxComponent Breadcrumb { get; } public MyPage() { Breadcrumb = new Breadcrumb(new[] { ("Home", "/"), ("Reports", "/reports"), ("Monthly", ""), }); } protected override void RenderBreadcrumb(HtmxRenderContext ctx) => Breadcrumb.Render(ctx.Next()); ``` --- ## Tips and tricks - Always make the last item the current page — its href is ignored anyway, and it gets the visual "active" treatment automatically. - If you have a non-navigable segment (e.g. a category separator with no URL), pass an empty `Href` for that item and it will render as a plain span. - For very deep hierarchies, consider truncating the middle items and replacing them with a `…` span — build the items list conditionally before passing to the constructor. - The chevron separator is `aria-hidden` so screen readers announce only the labels in sequence. --- ## Complete page example **`Templates/ArticlePage.htmx`** ```html
$$Breadcrumb$$

$$ArticleTitle$$

$$ArticleBody$$
``` **`Templates/ArticlePage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class ArticlePage : ArticlePageBase { private readonly IHtmxComponent _breadcrumb; private readonly byte[] _title; private readonly byte[] _body; public ArticlePage(string category, string categorySlug, Article article) { _breadcrumb = new Components.Breadcrumb(new[] { ("Home", "/"), ("Blog", "/blog"), (category, $"/blog/{categorySlug}"), (article.Title, ""), // current page }); _title = System.Net.WebUtility.HtmlEncode(article.Title).ToUtf8Bytes(); _body = article.HtmlContent.ToUtf8Bytes(); } protected override void RenderBreadcrumb(HtmxRenderContext ctx) => _breadcrumb.Render(ctx.Next()); protected override void RenderArticleTitle(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_title); protected override void RenderArticleBody(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_body); } ``` **GET handler** ```csharp [Handler] [MapGet("/blog/{category}/{slug}")] public static partial class GetArticleHandler { public record Query( [property: FromRoute] string Category, [property: FromRoute] string Slug); private static async Task HandleAsync( Query q, HttpContext ctx, ArticleService articles, CancellationToken ct) { var article = await articles.GetBySlugAsync(q.Slug, ct); if (article is null) return Results.NotFound(); var page = new ArticlePage(q.Category, q.Category.ToLower(), article); return await ctx.WriteHtmxPage(page, title: article.Title); } } ```