diff --git a/packages/chakra-components/docs/04-Pagination.mdx b/packages/chakra-components/docs/04-Pagination.mdx
new file mode 100644
index 00000000..202988ac
--- /dev/null
+++ b/packages/chakra-components/docs/04-Pagination.mdx
@@ -0,0 +1,51 @@
+---
+title: Pagination components
+description: Vocdoni Chakra components pagination
+---
+
+## Pagination and RoutedPagination
+
+You can easily add pagination to any method by using the included PaginationProvider (or RoutedPaginationProvider in
+case you want to use it with react-router-dom)
+
+```jsx
+const MyRoutedPaginatedComponent = () => {
+ // retrieve your data
+ const query = useQuery()
+
+
+ return (
+ {/* specify the total pages and the expected path for the router provider */}
+
+ {query.data.map((item) => (
+
+ ))}
+ {/* load the pagination itself */}
+
+
+ )
+}
+```
+
+The Pagination component will automatically handle the pagination state and will update the URL with the current page.
+
+The `PaginationProvider` (non routed) uses an internal state to handle the current page, rather than taking it from the URL.
+
+```jsx
+const MyPaginatedComponent = () => {
+ // retrieve your data
+ const query = useQuery()
+
+
+ return (
+ {/* specify the total pages for the router provider */}
+
+ {query.data.map((item) => (
+
+ ))}
+ {/* load the pagination itself */}
+
+
+ )
+}
+```
diff --git a/packages/chakra-components/src/components/Pagination.tsx b/packages/chakra-components/src/components/Pagination.tsx
new file mode 100644
index 00000000..0bd3be9a
--- /dev/null
+++ b/packages/chakra-components/src/components/Pagination.tsx
@@ -0,0 +1,184 @@
+import { Button, ButtonGroup, ButtonGroupProps, ButtonProps, Input, InputProps } from '@chakra-ui/react'
+import { usePagination, useRoutedPagination } from '@vocdoni/react-providers'
+import { ReactElement, useMemo, useState } from 'react'
+import { generatePath, Link as RouterLink, useLocation, useNavigate, useParams } from 'react-router-dom'
+
+export type PaginationProps = ButtonGroupProps & {
+ maxButtons?: number | false
+ buttonProps?: ButtonProps
+ inputProps?: InputProps
+}
+
+const createButton = (page: number, currentPage: number, props: ButtonProps) => (
+
+)
+
+type EllipsisButtonProps = ButtonProps & {
+ gotoPage: (page: number) => void
+ inputProps?: InputProps
+}
+
+const EllipsisButton = ({ gotoPage, inputProps, ...rest }: EllipsisButtonProps) => {
+ const [ellipsisInput, setEllipsisInput] = useState(false)
+
+ if (ellipsisInput) {
+ return (
+ {
+ if (e.target instanceof HTMLInputElement && e.key === 'Enter') {
+ const pageNumber = Number(e.target.value)
+ gotoPage(pageNumber)
+ setEllipsisInput(false)
+ }
+ }}
+ onBlur={() => setEllipsisInput(false)}
+ autoFocus
+ />
+ )
+ }
+
+ return (
+
+ )
+}
+
+const usePaginationPages = (
+ currentPage: number,
+ totalPages: number | undefined,
+ maxButtons: number | undefined | false,
+ gotoPage: (page: number) => void,
+ createPageButton: (i: number) => ReactElement,
+ inputProps?: InputProps,
+ buttonProps?: ButtonProps
+) => {
+ return useMemo(() => {
+ if (totalPages === undefined) return []
+
+ let pages: ReactElement[] = []
+
+ // Create an array of all page buttons
+ for (let i = 0; i < totalPages; i++) {
+ pages.push(createPageButton(i))
+ }
+
+ if (!maxButtons || totalPages <= maxButtons) {
+ return pages
+ }
+
+ const startEllipsis = (
+
+ )
+ const endEllipsis = (
+
+ )
+
+ // Add ellipsis and slice the array accordingly
+ const sideButtons = 2 // First and last page
+ const availableButtons = maxButtons - sideButtons // Buttons we can distribute around the current page
+
+ if (currentPage <= availableButtons / 2) {
+ // Near the start
+ return [...pages.slice(0, availableButtons), endEllipsis, pages[totalPages - 1]]
+ } else if (currentPage >= totalPages - 1 - availableButtons / 2) {
+ // Near the end
+ return [pages[0], startEllipsis, ...pages.slice(totalPages - availableButtons, totalPages)]
+ } else {
+ // In the middle
+ const startPage = currentPage - Math.floor((availableButtons - 1) / 2)
+ const endPage = currentPage + Math.floor(availableButtons / 2)
+ return [pages[0], startEllipsis, ...pages.slice(startPage, endPage - 1), endEllipsis, pages[totalPages - 1]]
+ }
+ }, [currentPage, totalPages, maxButtons, gotoPage])
+}
+
+export const Pagination = ({ maxButtons = 10, buttonProps, inputProps, ...rest }: PaginationProps) => {
+ const { page, setPage, totalPages } = usePagination()
+
+ const pages = usePaginationPages(
+ page,
+ totalPages,
+ maxButtons ? Math.max(5, maxButtons) : false,
+ (page) => {
+ if (page >= 0 && totalPages && page < totalPages) {
+ setPage(page)
+ }
+ },
+ (i) => createButton(i, page, { onClick: () => setPage(i), ...buttonProps })
+ )
+
+ return (
+
+ {totalPages === undefined ? (
+ <>
+
+
+ >
+ ) : (
+ pages
+ )}
+
+ )
+}
+
+export const RoutedPagination = ({ maxButtons = 10, buttonProps, ...rest }: PaginationProps) => {
+ const { path, totalPages } = useRoutedPagination()
+ const { search } = useLocation()
+ const { page, ...extraParams }: { page?: number } = useParams()
+ const navigate = useNavigate()
+
+ const p = Number(page) || 1
+
+ const _generatePath = (page: number) => generatePath(path, { page, ...extraParams }) + search
+
+ const pages = usePaginationPages(
+ p,
+ totalPages,
+ maxButtons ? Math.max(5, maxButtons) : false,
+ (page) => {
+ if (page >= 0 && totalPages && page < totalPages) {
+ navigate(_generatePath(page))
+ }
+ },
+ (i) => (
+
+ )
+ )
+
+ return (
+
+ {totalPages === undefined ? (
+ <>
+
+
+ >
+ ) : (
+ pages
+ )}
+
+ )
+}
diff --git a/packages/react-providers/src/index.ts b/packages/react-providers/src/index.ts
index 5fc44f53..ed9a74f8 100644
--- a/packages/react-providers/src/index.ts
+++ b/packages/react-providers/src/index.ts
@@ -2,5 +2,6 @@ export * from './client'
export * from './election'
export * from './i18n'
export * from './organization'
+export * from './pagination'
export type { ErrorPayload, RecursivePartial } from './types'
export * from './utils'
diff --git a/packages/react-providers/src/pagination/PaginationProvider.tsx b/packages/react-providers/src/pagination/PaginationProvider.tsx
new file mode 100644
index 00000000..b17be5bf
--- /dev/null
+++ b/packages/react-providers/src/pagination/PaginationProvider.tsx
@@ -0,0 +1,50 @@
+import { createContext, PropsWithChildren, useContext, useState } from 'react'
+
+export type PaginationContextProps = {
+ page: number
+ setPage: (page: number) => void
+ totalPages?: number
+}
+
+export type RoutedPaginationContextProps = Omit & {
+ path: string
+}
+
+const PaginationContext = createContext(undefined)
+const RoutedPaginationContext = createContext(undefined)
+
+export const usePagination = (): PaginationContextProps => {
+ const context = useContext(PaginationContext)
+ if (!context) {
+ throw new Error('usePagination must be used within a PaginationProvider')
+ }
+ return context
+}
+
+export const useRoutedPagination = (): RoutedPaginationContextProps => {
+ const context = useContext(RoutedPaginationContext)
+ if (!context) {
+ throw new Error('useRoutedPagination must be used within a RoutedPaginationProvider')
+ }
+ return context
+}
+
+export type PaginationProviderProps = Pick
+
+export type RoutedPaginationProviderProps = PaginationProviderProps & {
+ path: string
+}
+
+export const RoutedPaginationProvider = ({
+ totalPages,
+ path,
+ ...rest
+}: PropsWithChildren) => {
+ return
+}
+
+export const PaginationProvider = ({ totalPages, ...rest }: PropsWithChildren) => {
+ const [page, setPage] = useState(0)
+
+ return
+}
diff --git a/packages/react-providers/src/pagination/index.ts b/packages/react-providers/src/pagination/index.ts
new file mode 100644
index 00000000..24823042
--- /dev/null
+++ b/packages/react-providers/src/pagination/index.ts
@@ -0,0 +1 @@
+export * from './PaginationProvider'