Navigation
Jump To Page
Pagination with a jump field: the number is typed and submitted by a form, and a wrong value explains the allowed range instead of staying silent.
- pagination
- jump
- form
- validation
Preview
Use it with AI
- 1. Copy the link.
- 2. Write to your agent in your own words and drop the link into the sentence.
- 3. The agent opens the link and installs the component from the registry.
put this in the header: https://vibeui.ru/c/pagination-008?lang=en
Show the full instructions
What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.
# Install and place "pagination-008" (Jump To Page) from VibeUI
## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/pagination-008.json
Registry item: https://vibeui.ru/r/pagination-008.json
Installs to: components/vibeui/pagination-008.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.
Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.
## 2. What it is
Pagination with a jump field: the number is typed and submitted by a form, and a wrong value explains the allowed range instead of staying silent.
Pagination for a long list: arrows with a page indicator on top and a jump-to-page field below. Input is validated, the error is spelled out in words and tied to the field. Zero dependencies, one file, its own palette.
## 3. How to use it
import { Pagination008 } from "@/components/vibeui/pagination-008"
<Pagination008 total={148} defaultPage={37} label="Go to page" />
Read the installed file for the full prop list.
## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.
Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)
## 5. Keep exactly as installed
- the local --vibeui-pagination-008-* palette — do not swap it for your theme tokens (bg-input, border-destructive and the like)
- the real <form> around the field: Enter submits it by itself, so a separate key handler is neither needed nor forgotten
- the aria-describedby linking the field to the hint and to the error — otherwise the text beside the field is just a paragraph
- role="alert" on the error message: without it the message is not announced when it appears
- the hint about the page count in the calm state — the message slot is always occupied, so the layout never jumps
- a <label> on the field rather than a placeholder alone: a placeholder disappears while typing and cannot be the name
## 6. You may change
- total and defaultPage — the page count and the starting page
- label — the caption of the jump field
- the hint and error texts
- the accent through the accent prop — it colours the Go button and the focus rings
## 7. Rules
- The component is a client one: the page state and the input draft live inside it.
- The field does not hard-limit typing: min and max are hints to the browser, the check still belongs in the handler.
- A jump field pays off on lists of dozens of pages: on five it only gets in the way.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.
## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.For developers
npx shadcn@latest add https://vibeui.ru/r/pagination-008.jsonhttps://vibeui.ru/r/pagination-008.jsonКомпонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-pagination-008-*. Клиентский: "use client" ради страницы и черновика ввода. Поле перехода лежит в настоящей <form>, поэтому Enter отправляет её без отдельного обработчика клавиши. Неверный номер помечает поле через aria-invalid и показывает сообщение с role="alert", связанное с полем через aria-describedby; то же место в спокойном состоянии занимает подсказка об общем числе страниц.
Component source
The same file your agent installs. Here in case you would rather copy it by hand.
"use client"
import { useState } from "react"
import type { CSSProperties, FormEvent } from "react"
export type Pagination008Props = {
total?: number
defaultPage?: number
label?: string
accent?: string
className?: string
style?: CSSProperties
}
// Идея компонента: пагинация для длинного списка, где номера бесполезны, а
// прыжок нужен. Поле перехода живёт в настоящей <form>: Enter отправляет её, и
// не нужен отдельный обработчик клавиши. Неверный номер не молчит — форма
// показывает подсказку рядом с полем и связывает её через aria-describedby.
const STYLES = `
:where([data-vibeui-block="pagination-008"]){
--vibeui-pagination-008-bg:oklch(1 0 0);
--vibeui-pagination-008-fg:oklch(0.24 0.014 265);
--vibeui-pagination-008-muted:oklch(0.55 0.014 265);
--vibeui-pagination-008-border:oklch(0.91 0.006 265);
--vibeui-pagination-008-field:oklch(0.97 0.003 265);
--vibeui-pagination-008-hover:oklch(0.55 0.02 265 / 8%);
--vibeui-pagination-008-bad:oklch(0.58 0.19 26);
--vibeui-pagination-008-accent:oklch(0.55 0.2 262);
--vibeui-pagination-008-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="pagination-008"]{
box-sizing:border-box;width:100%;max-width:30rem;padding:0.625rem;
background:var(--vibeui-pagination-008-bg);color:var(--vibeui-pagination-008-fg);
border:1px solid var(--vibeui-pagination-008-border);border-radius:0.75rem;
font-family:var(--vibeui-pagination-008-font);
}
[data-vibeui-block="pagination-008"] [data-part="row"]{
display:flex;align-items:center;gap:0.375rem;flex-wrap:wrap;
}
[data-vibeui-block="pagination-008"] [data-part="step"]{
appearance:none;cursor:pointer;
height:2rem;padding:0 0.75rem;box-sizing:border-box;
display:inline-flex;align-items:center;gap:0.375rem;
border:1px solid var(--vibeui-pagination-008-border);border-radius:0.5rem;
background:none;color:inherit;font:inherit;font-size:0.8125rem;
}
[data-vibeui-block="pagination-008"] [data-part="step"]:hover:not(:disabled){background:var(--vibeui-pagination-008-hover)}
[data-vibeui-block="pagination-008"] [data-part="step"]:focus-visible{outline:2px solid var(--vibeui-pagination-008-accent);outline-offset:1px}
[data-vibeui-block="pagination-008"] [data-part="step"]:disabled{opacity:.45;cursor:default}
[data-vibeui-block="pagination-008"] [data-part="now"]{
margin:0 auto;font-size:0.8125rem;color:var(--vibeui-pagination-008-muted);
font-variant-numeric:tabular-nums;
}
[data-vibeui-block="pagination-008"] [data-part="now"] b{color:var(--vibeui-pagination-008-fg);font-weight:650}
[data-vibeui-block="pagination-008"] [data-part="form"]{
display:flex;align-items:center;gap:0.375rem;margin-top:0.5rem;
padding-top:0.5rem;border-top:1px solid var(--vibeui-pagination-008-border);
}
[data-vibeui-block="pagination-008"] [data-part="label"]{font-size:0.8125rem;color:var(--vibeui-pagination-008-muted)}
[data-vibeui-block="pagination-008"] input{
width:4.5rem;height:2rem;padding:0 0.5rem;box-sizing:border-box;
border:1px solid var(--vibeui-pagination-008-border);border-radius:0.5rem;
background:var(--vibeui-pagination-008-field);color:inherit;
font:inherit;font-size:0.8125rem;font-variant-numeric:tabular-nums;
}
[data-vibeui-block="pagination-008"] input:focus-visible{outline:2px solid var(--vibeui-pagination-008-accent);outline-offset:-1px;border-color:transparent}
[data-vibeui-block="pagination-008"] input[aria-invalid="true"]{border-color:var(--vibeui-pagination-008-bad)}
[data-vibeui-block="pagination-008"] [data-part="go"]{
appearance:none;cursor:pointer;height:2rem;padding:0 0.875rem;box-sizing:border-box;
border:0;border-radius:0.5rem;
background:var(--vibeui-pagination-008-accent);color:oklch(1 0 0);
font:inherit;font-size:0.8125rem;font-weight:600;
}
[data-vibeui-block="pagination-008"] [data-part="go"]:focus-visible{outline:2px solid var(--vibeui-pagination-008-accent);outline-offset:2px}
[data-vibeui-block="pagination-008"] [data-part="error"]{
margin:0;font-size:0.75rem;color:var(--vibeui-pagination-008-bad);
}
[data-vibeui-block="pagination-008"] [data-part="hint"]{
margin:0;font-size:0.75rem;color:var(--vibeui-pagination-008-muted);
font-variant-numeric:tabular-nums;
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="pagination-008"] *{animation:none!important;transition:none!important}}
`
/**
* Пагинация с полем перехода к произвольному номеру страницы.
* Один файл, ноль зависимостей, собственная палитра.
*/
export function Pagination008({
total = 148,
defaultPage = 37,
label = "Перейти к странице",
accent,
className,
style,
}: Pagination008Props) {
const [page, setPage] = useState(Math.min(Math.max(defaultPage, 1), total))
const [draft, setDraft] = useState("")
const [bad, setBad] = useState(false)
const palette = {
...(accent ? { "--vibeui-pagination-008-accent": accent } : null),
...style,
} as CSSProperties
function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
const value = Number(draft)
if (!Number.isInteger(value) || value < 1 || value > total) {
setBad(true)
return
}
setBad(false)
setPage(value)
setDraft("")
}
return (
<>
<style href="vibeui-pagination-008" precedence="medium">
{STYLES}
</style>
<nav
data-vibeui-block="pagination-008"
aria-label="Навигация по страницам"
className={className}
style={palette}
>
<div data-part="row">
<button
type="button"
data-part="step"
disabled={page === 1}
onClick={() => setPage(Math.max(page - 1, 1))}
>
← Назад
</button>
<p data-part="now" aria-current="page">
Страница <b>{page}</b> из {total}
</p>
<button
type="button"
data-part="step"
disabled={page === total}
onClick={() => setPage(Math.min(page + 1, total))}
>
Вперёд →
</button>
</div>
<form data-part="form" onSubmit={onSubmit}>
<label data-part="label" htmlFor="vibeui-pagination-008-field">
{label}
</label>
<input
id="vibeui-pagination-008-field"
type="number"
inputMode="numeric"
min={1}
max={total}
value={draft}
placeholder={String(page)}
aria-invalid={bad || undefined}
aria-describedby="vibeui-pagination-008-note"
onChange={(event) => {
setDraft(event.target.value)
setBad(false)
}}
/>
<button type="submit" data-part="go">
Перейти
</button>
</form>
<p
id="vibeui-pagination-008-note"
data-part={bad ? "error" : "hint"}
role={bad ? "alert" : undefined}
>
{bad ? `Введите номер от 1 до ${total}` : `Всего страниц: ${total}`}
</p>
</nav>
</>
)
}