diff --git a/src/posts/PaginationTagHelpers.md b/src/posts/PaginationTagHelpers.md
new file mode 100644
index 0000000..6ccab03
--- /dev/null
+++ b/src/posts/PaginationTagHelpers.md
@@ -0,0 +1,215 @@
+---
+title: Pagination Tag Helpers
+description: A couple of tag helpers to automate creating common pagination components
+tags: [ Programming, C#, ASP.NET Core ]
+---
+
+These tag helpers work with [`PaginatedResults`](/posts/CsPaginationTools) to automatically create things like a results header (e.g. "Showing results 1-20 of 123" and "Page 1 of 7") as well as pagination links.
+
+## How to use
+
+```csharp
+
+
+@foreach (var result in this.Model.MyResults.Results)
+{
+ ...
+}
+
+
+```
+
+The query parameter given as `page-number-key` will be changed or added to each the pagination link. The `page-number-key` query parameter will be left off for links to page 1.
+
+## Code
+
+The `pager-details` tag helper:
+
+```csharp
+using ASIS.SecretsManagement.Shared;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Mvc.TagHelpers;
+using Microsoft.AspNetCore.Razor.TagHelpers;
+using System.Text.Encodings.Web;
+
+///
+/// Displays details about pagination in the form "Showing results x - y of z" on the left and
+/// "Page x of y" on the right
+///
+public class PagerDetailsTagHelper : TagHelper
+{
+ public PaginatedResults Results { get; set; }
+
+ public override void Process(TagHelperContext context, TagHelperOutput output)
+ {
+ output.TagName = "div";
+ output.TagMode = TagMode.StartTagAndEndTag;
+ output.AddClass("paginationDetails", HtmlEncoder.Default);
+
+ TagBuilder resultsNumDetails = new("span");
+ resultsNumDetails.InnerHtml.Append("Showing results "
+ + $"{this.Results.FirstResultNumber} - {this.Results.LastResultNumber} "
+ + $"of {this.Results.TotalResultsCount}");
+ output.Content.AppendHtml(resultsNumDetails);
+
+ TagBuilder pageNumDetails = new("span");
+ pageNumDetails.InnerHtml.Append(
+ $"Page {this.Results.PageNumber} of {this.Results.LastPageNumber}");
+ output.Content.AppendHtml(pageNumDetails);
+ }
+}
+```
+
+And the `pager` tag helper:
+
+```csharp
+using ASIS.SecretsManagement.Shared;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Microsoft.AspNetCore.Razor.TagHelpers;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Primitives;
+using System.Collections.Immutable;
+
+///
+/// Creates a list of links to pages for the current list of data. This will replace the page number
+/// parameter in the query string using the .
+///
+///
+/// Page 1 is assumed to be the default and the page number parameter will be excluded
+///
+public class PagerTagHelper : TagHelper
+{
+ /// The name of the page number parameter in the query string
+ public string PageNumberKey { get; set; }
+ public PaginatedResults Results { get; set; }
+
+ [ViewContext, HtmlAttributeNotBound]
+ public ViewContext ViewContext { get; set; }
+
+ private string CurrentPagePath { get => this.ViewContext.HttpContext.Request.Path; }
+ private ImmutableDictionary QueryStringValues { get; set; }
+
+ public override void Process(TagHelperContext context, TagHelperOutput output)
+ {
+ output.TagName = "nav";
+ output.TagMode = TagMode.StartTagAndEndTag;
+ output.Attributes.Add("aria-label", "pagination");
+
+ var paginationList = new TagBuilder("ul");
+ paginationList.AddCssClass("pagination");
+
+ this.QueryStringValues = QueryHelpers
+ .ParseQuery(this.ViewContext.HttpContext.Request.QueryString.Value)
+ .ToImmutableDictionary()
+ .Remove(this.PageNumberKey);
+
+ paginationList.InnerHtml.AppendHtml(this.CreatePreviousPageItem());
+
+ foreach (int i in Enumerable.Range(1, this.Results.LastPageNumber))
+ {
+ paginationList.InnerHtml.AppendHtml(this.CreatePageItem(i, i.ToString()));
+ }
+
+ paginationList.InnerHtml.AppendHtml(this.CreateNextPageItem());
+
+ output.Content.AppendHtml(paginationList);
+ }
+
+ private TagBuilder CreatePageItem(int pageNumber, string linkText)
+ {
+ var listItem = new TagBuilder("li");
+ var link = new TagBuilder("a");
+
+ if (pageNumber == this.Results.PageNumber)
+ link.AddCssClass("currentPage");
+
+ link.InnerHtml.AppendHtml(linkText);
+
+ // Leave off the number for page 1
+ var queryStringWithPageNumber = pageNumber == 1
+ ? this.QueryStringValues
+ : this.QueryStringValues.Add(this.PageNumberKey, pageNumber.ToString());
+
+ link.Attributes.Add("href", QueryHelpers.AddQueryString(
+ this.CurrentPagePath, queryStringWithPageNumber));
+
+ listItem.InnerHtml.AppendHtml(link);
+ return listItem;
+ }
+
+ private TagBuilder CreatePreviousPageItem()
+ {
+ var listItem = new TagBuilder("li");
+ var link = new TagBuilder("a");
+
+ if (this.Results.IsFirstPage)
+ link.AddCssClass("disabled");
+
+ // Leave off the number for page 1
+ var queryStringWithPageNumber = this.Results.PreviousPageNumber == 1
+ ? this.QueryStringValues
+ : this.QueryStringValues
+ .Add(this.PageNumberKey, this.Results.PreviousPageNumber.ToString());
+
+ link.Attributes.Add("href", QueryHelpers.AddQueryString(
+ this.CurrentPagePath, queryStringWithPageNumber));
+
+ link.InnerHtml.AppendHtml("«");
+
+ listItem.InnerHtml.AppendHtml(link);
+ return listItem;
+ }
+
+ private TagBuilder CreateNextPageItem()
+ {
+ var listItem = new TagBuilder("li");
+ var link = new TagBuilder("a");
+
+ if (this.Results.IsLastPage)
+ link.AddCssClass("disabled");
+
+ // Leave off the number for page 1
+ var queryStringWithPageNumber = this.Results.NextPageNumber == 1
+ ? this.QueryStringValues
+ : this.QueryStringValues.Add(this.PageNumberKey, this.Results.NextPageNumber.ToString());
+
+ link.Attributes.Add("href", QueryHelpers.AddQueryString(
+ this.CurrentPagePath, queryStringWithPageNumber));
+
+ link.InnerHtml.AppendHtml("»");
+
+ listItem.InnerHtml.AppendHtml(link);
+ return listItem;
+ }
+}
+
+public static class PaginatedResultsExtensions
+{
+ /// Razor doesn't like casting, so use this instead
+ public static PaginatedResults ToObjectPaginatedResults(
+ this PaginatedResults paginatedResults) where T : class
+ {
+ return new PaginatedResults(paginatedResults.PageNumber,
+ paginatedResults.ResultsPerPage, paginatedResults.Results,
+ paginatedResults.TotalResultsCount);
+ }
+}
+```
+
+And some CSS for the `pager-details`:
+
+```css
+.paginationDetails {
+ display: flex;
+ flex-wrap: wrap;
+ row-gap: 0.25em;
+ column-gap: 1em;
+ margin-bottom: 1em;
+}
+
+.paginationDetails:first-child {
+ margin-right: auto;
+}
+```