# Page tabs

Page navigation as a row of links: the pages of one section of the app ("Overview", "Activity", "Settings"), the one the person is on marked `aria-current="page"`. Each is a real link, drawn through the app's link render (`LinkProvider`, `CandorProvider`'s `link`), so it opens in a new tab, is copied and goes through the router. It is a `nav`, not a `tablist`: choosing a tab here changes the page, not what a panel shows. For switching content within a page, use Tabs.

Where the row is wider than its space it scrolls sideways. A fade at the edge that has more is drawn only while there is more in that direction, and follows the reading direction. The fade is not the only sign: every link stays reachable with Tab, which scrolls it into view, and while the row has more to scroll to it can itself take focus to be moved with the arrow keys. The current tab is scrolled into view when the row first draws and when `current` changes, without moving focus.

- Registry name: `page-tabs` (`@candor/page-tabs`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/page-tabs

## Install

```bash
npx candor-ui@3 add page-tabs
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/page-tabs`.
It brings in: bidi, link, locale, tokens, utils.

## Example

```tsx
"use client"

import * as React from "react"
import { PageTabs } from "@/components/candor/page-tabs"

const tabs = [
  { id: "overview", label: "Overview", href: "#overview" },
  { id: "activity", label: "Activity", href: "#activity" },
  { id: "runs", label: "Runs", href: "#runs" },
  { id: "approvals", label: "Approvals", href: "#approvals" },
  { id: "members", label: "Members", href: "#members" },
  { id: "billing", label: "Billing", href: "#billing" },
  { id: "settings", label: "Settings", href: "#settings" },
]

/** In an app, `current` comes from the router's address; here the hash stands in for it. */
export default function Example() {
  const [current, setCurrent] = React.useState("activity")
  React.useEffect(() => {
    const read = () => {
      const id = window.location.hash.slice(1)
      if (tabs.some((t) => t.id === id)) setCurrent(id)
    }
    window.addEventListener("hashchange", read)
    return () => window.removeEventListener("hashchange", read)
  }, [])
  return (
    <div className="max-w-xs">
      <PageTabs label="Project" tabs={tabs} current={current} />
    </div>
  )
}
```

## <PageTabs>

Props:

- `tabs: PageTabsItem[]`: The pages, in the order to draw them.
- `current: string`: The `id` of the page the person is on; that tab gets `aria-current="page"`. The app knows the page, so it is required. An id no tab has marks none.
- `label?: string | undefined`: The navigation's accessible name. "Pages" by default; name what the pages are of ("Project").
- `className?: string | undefined`: Classes for the navigation, merged with Candor's.

## Strings (`pageTabs`)

The fixed words, English by default. Override any of them under `pageTabs` in `<LocaleProvider strings={{ pageTabs: { … } }}>`; the table is exported as `pageTabsStrings`.

- `label`: Pages

## Types

### PageTabsItem

One page `PageTabs` links to.

```ts
{
  /** Which page it is; compared with `PageTabs`' `current`. */
  id: string;
  /** The words for it. Drawn as text, whatever they hold. */
  label: string;
  /** Where it goes: an address in the app ("/projects/42/activity"), followed through the app's link render. */
  href: string;
}
```

## Motion

- **tint** (tint): A tab's text colour changes when the pointer is over it, and a ring fades in when it takes keyboard focus. The current tab is drawn in its colours at once, and scrolling the current tab into view happens at once. Under reduced motion or the calm level the changes are immediate.

Set with `--cd-motion-page-tabs-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
