Pagination
Navigation controls for moving between pages of content. HTMX ready.
TailwindCSS
package showcase
import (
"github.com/axzilla/templui/components"
)
templ PaginationDefault() {
@components.Pagination(components.PaginationProps{
Class: "mt-8",
}) {
@components.PaginationContent() {
@components.PaginationItem() {
@components.PaginationPrevious(components.PaginationPreviousProps{
Href: "?page=1",
Disabled: false,
})
}
@components.PaginationItem() {
@components.PaginationLink(components.PaginationLinkProps{
Href: "?page=1",
}) {
1
}
}
@components.PaginationItem() {
@components.PaginationLink(components.PaginationLinkProps{
Href: "?page=2",
IsActive: true,
}) {
2
}
}
@components.PaginationItem() {
@components.PaginationLink(components.PaginationLinkProps{
Href: "?page=3",
}) {
3
}
}
@components.PaginationItem() {
@components.PaginationEllipsis()
}
@components.PaginationItem() {
@components.PaginationNext(components.PaginationNextProps{
Href: "?page=3",
})
}
}
}
}
package components
import (
"github.com/axzilla/templui/icons"
"github.com/axzilla/templui/utils"
)
type PaginationProps struct {
ID string
Class string
Attributes templ.Attributes
}
type PaginationContentProps struct {
ID string
Class string
Attributes templ.Attributes
}
type PaginationItemProps struct {
ID string
Class string
Attributes templ.Attributes
}
type PaginationLinkProps struct {
ID string
Class string
Attributes templ.Attributes
Href string
IsActive bool
Disabled bool
HxGet string
HxTarget string
HxSwap string
}
type PaginationPreviousProps struct {
ID string
Class string
Attributes templ.Attributes
Href string
Disabled bool
HxGet string
HxTarget string
HxSwap string
}
type PaginationNextProps struct {
ID string
Class string
Attributes templ.Attributes
Href string
Disabled bool
HxGet string
HxTarget string
HxSwap string
}
templ Pagination(props ...PaginationProps) {
{{ var p PaginationProps }}
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 PaginationContent(props ...PaginationContentProps) {
{{ var p PaginationContentProps }}
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 PaginationItem(props ...PaginationItemProps) {
{{ var p PaginationItemProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<li
if p.ID != "" {
id={ p.ID }
}
{ p.Attributes... }
>
{ children... }
</li>
}
templ PaginationLink(props ...PaginationLinkProps) {
{{ var p PaginationLinkProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.Disabled {
@Button(ButtonProps{
ID: p.ID,
Disabled: true,
Size: ButtonSizeIcon,
Variant: ButtonVariantGhost,
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
} else if p.HxGet != "" {
@Button(ButtonProps{
ID: p.ID,
HxGet: p.HxGet,
HxTarget: p.HxTarget,
HxSwap: p.HxSwap,
Size: ButtonSizeIcon,
Variant: buttonVariant(p.IsActive),
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
} else {
@Button(ButtonProps{
ID: p.ID,
Href: p.Href,
Size: ButtonSizeIcon,
Variant: buttonVariant(p.IsActive),
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
}
}
templ PaginationPrevious(props ...PaginationPreviousProps) {
{{ var p PaginationPreviousProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
@Button(ButtonProps{
ID: p.ID,
Href: p.Href,
HxGet: p.HxGet,
HxTarget: p.HxTarget,
HxSwap: p.HxSwap,
Disabled: p.Disabled,
Variant: ButtonVariantGhost,
Class: utils.TwMerge("gap-1", p.Class),
Attributes: p.Attributes,
}) {
@icons.ChevronLeft(icons.IconProps{Size: 16})
<span>Previous</span>
}
}
templ PaginationNext(props ...PaginationNextProps) {
{{ var p PaginationNextProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
@Button(ButtonProps{
ID: p.ID,
Href: p.Href,
HxGet: p.HxGet,
HxTarget: p.HxTarget,
HxSwap: p.HxSwap,
Disabled: p.Disabled,
Variant: ButtonVariantGhost,
Class: utils.TwMerge("gap-1", p.Class),
Attributes: p.Attributes,
}) {
<span>Next</span>
@icons.ChevronRight(icons.IconProps{Size: 16})
}
}
templ PaginationEllipsis() {
@icons.Ellipsis(icons.IconProps{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) ButtonVariant {
if isActive {
return ButtonVariantOutline
}
return ButtonVariantGhost
}
Usage
@components.Pagination(components.PaginationProps{...})
Examples
With Helper
package showcase
import (
"fmt"
"github.com/axzilla/templui/components"
)
templ PaginationWithHelper() {
{{ pagination := components.CreatePagination(5, 20, 3) }}
@components.Pagination() {
@components.PaginationContent() {
@components.PaginationItem() {
@components.PaginationPrevious(components.PaginationPreviousProps{
Href: fmt.Sprintf("?page=%d", pagination.CurrentPage-1),
Disabled: !pagination.HasPrevious,
})
}
// First page with ellipsis if needed
if pagination.Pages[0] > 1 {
@components.PaginationItem() {
@components.PaginationLink(components.PaginationLinkProps{
Href: "?page=1",
}) {
1
}
}
if pagination.Pages[0] > 2 {
@components.PaginationItem() {
@components.PaginationEllipsis()
}
}
}
// Visible pages
for _, page := range pagination.Pages {
@components.PaginationItem() {
@components.PaginationLink(components.PaginationLinkProps{
Href: fmt.Sprintf("?page=%d", page),
IsActive: page == pagination.CurrentPage,
}) {
{ fmt.Sprint(page) }
}
}
}
// Last page with ellipsis if needed
if pagination.Pages[len(pagination.Pages)-1] < pagination.TotalPages {
if pagination.Pages[len(pagination.Pages)-1] < pagination.TotalPages-1 {
@components.PaginationItem() {
@components.PaginationEllipsis()
}
}
@components.PaginationItem() {
@components.PaginationLink(components.PaginationLinkProps{
Href: fmt.Sprintf("?page=%d", pagination.TotalPages),
}) {
{ fmt.Sprint(pagination.TotalPages) }
}
}
}
@components.PaginationItem() {
@components.PaginationNext(components.PaginationNextProps{
Href: fmt.Sprintf("?page=%d", pagination.CurrentPage+1),
Disabled: !pagination.HasNext,
})
}
}
}
}
package components
import (
"github.com/axzilla/templui/icons"
"github.com/axzilla/templui/utils"
)
type PaginationProps struct {
ID string
Class string
Attributes templ.Attributes
}
type PaginationContentProps struct {
ID string
Class string
Attributes templ.Attributes
}
type PaginationItemProps struct {
ID string
Class string
Attributes templ.Attributes
}
type PaginationLinkProps struct {
ID string
Class string
Attributes templ.Attributes
Href string
IsActive bool
Disabled bool
HxGet string
HxTarget string
HxSwap string
}
type PaginationPreviousProps struct {
ID string
Class string
Attributes templ.Attributes
Href string
Disabled bool
HxGet string
HxTarget string
HxSwap string
}
type PaginationNextProps struct {
ID string
Class string
Attributes templ.Attributes
Href string
Disabled bool
HxGet string
HxTarget string
HxSwap string
}
templ Pagination(props ...PaginationProps) {
{{ var p PaginationProps }}
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 PaginationContent(props ...PaginationContentProps) {
{{ var p PaginationContentProps }}
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 PaginationItem(props ...PaginationItemProps) {
{{ var p PaginationItemProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<li
if p.ID != "" {
id={ p.ID }
}
{ p.Attributes... }
>
{ children... }
</li>
}
templ PaginationLink(props ...PaginationLinkProps) {
{{ var p PaginationLinkProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
if p.Disabled {
@Button(ButtonProps{
ID: p.ID,
Disabled: true,
Size: ButtonSizeIcon,
Variant: ButtonVariantGhost,
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
} else if p.HxGet != "" {
@Button(ButtonProps{
ID: p.ID,
HxGet: p.HxGet,
HxTarget: p.HxTarget,
HxSwap: p.HxSwap,
Size: ButtonSizeIcon,
Variant: buttonVariant(p.IsActive),
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
} else {
@Button(ButtonProps{
ID: p.ID,
Href: p.Href,
Size: ButtonSizeIcon,
Variant: buttonVariant(p.IsActive),
Class: p.Class,
Attributes: p.Attributes,
}) {
{ children... }
}
}
}
templ PaginationPrevious(props ...PaginationPreviousProps) {
{{ var p PaginationPreviousProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
@Button(ButtonProps{
ID: p.ID,
Href: p.Href,
HxGet: p.HxGet,
HxTarget: p.HxTarget,
HxSwap: p.HxSwap,
Disabled: p.Disabled,
Variant: ButtonVariantGhost,
Class: utils.TwMerge("gap-1", p.Class),
Attributes: p.Attributes,
}) {
@icons.ChevronLeft(icons.IconProps{Size: 16})
<span>Previous</span>
}
}
templ PaginationNext(props ...PaginationNextProps) {
{{ var p PaginationNextProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
@Button(ButtonProps{
ID: p.ID,
Href: p.Href,
HxGet: p.HxGet,
HxTarget: p.HxTarget,
HxSwap: p.HxSwap,
Disabled: p.Disabled,
Variant: ButtonVariantGhost,
Class: utils.TwMerge("gap-1", p.Class),
Attributes: p.Attributes,
}) {
<span>Next</span>
@icons.ChevronRight(icons.IconProps{Size: 16})
}
}
templ PaginationEllipsis() {
@icons.Ellipsis(icons.IconProps{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) ButtonVariant {
if isActive {
return ButtonVariantOutline
}
return ButtonVariantGhost
}