Pagination
Navigation controls for moving between pages of content. HTMX ready.
TailwindCSS
package showcase
import "github.com/axzilla/templui/internal/components/pagination"
templ PaginationDefault() {
@pagination.Pagination(pagination.Props{
Class: "mt-8",
}) {
@pagination.Content() {
@pagination.Item() {
@pagination.Previous(pagination.PreviousProps{
Href: "?page=1",
Disabled: false,
Label: "Previous",
})
}
@pagination.Item() {
@pagination.Link(pagination.LinkProps{
Href: "?page=1",
}) {
1
}
}
@pagination.Item() {
@pagination.Link(pagination.LinkProps{
Href: "?page=2",
IsActive: true,
}) {
2
}
}
@pagination.Item() {
@pagination.Link(pagination.LinkProps{
Href: "?page=3",
}) {
3
}
}
@pagination.Item() {
@pagination.Ellipsis()
}
@pagination.Item() {
@pagination.Next(pagination.NextProps{
Href: "?page=3",
Label: "Next",
})
}
}
}
}
Installation
templui add pagination
Copy and paste the following code into your project:
package pagination import ( "github.com/axzilla/templui/internal/components/button" "github.com/axzilla/templui/internal/components/icon" "github.com/axzilla/templui/internal/utils" ) type Props struct { ID string Class string Attributes templ.Attributes } type ContentProps struct { ID string Class string Attributes templ.Attributes } type ItemProps struct { ID string Class string Attributes templ.Attributes } type LinkProps struct { ID string Class string Attributes templ.Attributes Href string IsActive bool Disabled bool HxGet string HxTarget string HxSwap string } type PreviousProps struct { ID string Class string Attributes templ.Attributes Href string Disabled bool Label string HxGet string HxTarget string HxSwap string } type NextProps struct { ID string Class string Attributes templ.Attributes Href string Disabled bool Label string HxGet string HxTarget string HxSwap string } templ Pagination(props ...Props) { {{ var p Props }} if len(props) > 0 { {{ p = props[0] }} } <nav if p.ID != "" { id={ p.ID } } role="navigation" aria-label="pagination" class={ utils.TwMerge("flex flex-wrap justify-center", p.Class) } { p.Attributes... } > { children... } </nav> } templ Content(props ...ContentProps) { {{ var p ContentProps }} if len(props) > 0 { {{ p = props[0] }} } <ul if p.ID != "" { id={ p.ID } } class="flex flex-row items-center gap-1" { p.Attributes... } > { children... } </ul> } templ Item(props ...ItemProps) { {{ var p ItemProps }} if len(props) > 0 { {{ p = props[0] }} } <li if p.ID != "" { id={ p.ID } } { p.Attributes... } > { children... } </li> } templ Link(props ...LinkProps) { {{ var p LinkProps }} if len(props) > 0 { {{ p = props[0] }} } if p.Disabled { @button.Button(button.Props{ ID: p.ID, Disabled: true, Size: button.SizeIcon, Variant: button.VariantGhost, Class: p.Class, Attributes: p.Attributes, }) { { children... } } } else if p.HxGet != "" { @button.Button(button.Props{ ID: p.ID, HxGet: p.HxGet, HxTarget: p.HxTarget, HxSwap: p.HxSwap, Size: button.SizeIcon, Variant: button.Variant(buttonVariant(p.IsActive)), Class: p.Class, Attributes: p.Attributes, }) { { children... } } } else { @button.Button(button.Props{ ID: p.ID, Href: p.Href, Size: button.SizeIcon, Variant: button.Variant(buttonVariant(p.IsActive)), Class: p.Class, Attributes: p.Attributes, }) { { children... } } } } templ Previous(props ...PreviousProps) { {{ var p PreviousProps }} if len(props) > 0 { {{ p = props[0] }} } @button.Button(button.Props{ ID: p.ID, Href: p.Href, HxGet: p.HxGet, HxTarget: p.HxTarget, HxSwap: p.HxSwap, Disabled: p.Disabled, Variant: button.VariantGhost, Class: utils.TwMerge("gap-1", p.Class), Attributes: p.Attributes, }) { @icon.ChevronLeft(icon.Props{Size: 16}) if p.Label != "" { <span>{ p.Label }</span> } } } templ Next(props ...NextProps) { {{ var p NextProps }} if len(props) > 0 { {{ p = props[0] }} } @button.Button(button.Props{ ID: p.ID, Href: p.Href, HxGet: p.HxGet, HxTarget: p.HxTarget, HxSwap: p.HxSwap, Disabled: p.Disabled, Variant: button.VariantGhost, Class: utils.TwMerge("gap-1", p.Class), Attributes: p.Attributes, }) { if p.Label != "" { <span>{ p.Label }</span> } @icon.ChevronRight(icon.Props{Size: 16}) } } templ Ellipsis() { @icon.Ellipsis(icon.Props{Size: 16}) } func CreatePagination(currentPage, totalPages, maxVisible int) struct { CurrentPage int TotalPages int Pages []int HasPrevious bool HasNext bool } { if currentPage < 1 { currentPage = 1 } if totalPages < 1 { totalPages = 1 } if currentPage > totalPages { currentPage = totalPages } if maxVisible < 1 { maxVisible = 5 } start, end := calculateVisibleRange(currentPage, totalPages, maxVisible) pages := make([]int, 0, end-start+1) for i := start; i <= end; i++ { pages = append(pages, i) } return struct { CurrentPage int TotalPages int Pages []int HasPrevious bool HasNext bool }{ CurrentPage: currentPage, TotalPages: totalPages, Pages: pages, HasPrevious: currentPage > 1, HasNext: currentPage < totalPages, } } func calculateVisibleRange(currentPage, totalPages, maxVisible int) (int, int) { if totalPages <= maxVisible { return 1, totalPages } half := maxVisible / 2 start := currentPage - half end := currentPage + half if start < 1 { end += (1 - start) start = 1 } if end > totalPages { start -= (end - totalPages) if start < 1 { start = 1 } end = totalPages } return start, end } func buttonVariant(isActive bool) button.Variant { if isActive { return button.VariantOutline } return button.VariantGhost }
Update the import paths to match your project setup.
Examples
With Helper
package showcase
import (
"fmt"
"github.com/axzilla/templui/internal/components/pagination"
)
templ PaginationWithHelper() {
{{ p := pagination.CreatePagination(5, 20, 3) }}
@pagination.Pagination() {
@pagination.Content() {
@pagination.Item() {
@pagination.Previous(pagination.PreviousProps{
Href: fmt.Sprintf("?page=%d", p.CurrentPage-1),
Disabled: !p.HasPrevious,
Label: "Previous",
})
}
// First page with ellipsis if needed
if p.Pages[0] > 1 {
@pagination.Item() {
@pagination.Link(pagination.LinkProps{
Href: "?page=1",
}) {
1
}
}
if p.Pages[0] > 2 {
@pagination.Item() {
@pagination.Ellipsis()
}
}
}
// Visible pages
for _, page := range p.Pages {
@pagination.Item() {
@pagination.Link(pagination.LinkProps{
Href: fmt.Sprintf("?page=%d", page),
IsActive: page == p.CurrentPage,
}) {
{ fmt.Sprint(page) }
}
}
}
// Last page with ellipsis if needed
if p.Pages[len(p.Pages)-1] < p.TotalPages {
if p.Pages[len(p.Pages)-1] < p.TotalPages-1 {
@pagination.Item() {
@pagination.Ellipsis()
}
}
@pagination.Item() {
@pagination.Link(pagination.LinkProps{
Href: fmt.Sprintf("?page=%d", p.TotalPages),
}) {
{ fmt.Sprint(p.TotalPages) }
}
}
}
@pagination.Item() {
@pagination.Next(pagination.NextProps{
Href: fmt.Sprintf("?page=%d", p.CurrentPage+1),
Disabled: !p.HasNext,
Label: "Next",
})
}
}
}
}