Skip to content
Tier

Page tabs

Pro

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.

Markdown
Version 5.4.0· other versions

This page is for 5.4.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0, 4.3.0, 4.4.0, 4.5.0, 5.0.0, 5.1.0 and 5.2.0: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/page-tabs

Brings in: bidi, link, locale, tokens, utils

Usage

TSX · The example above
"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>

PropTypeDefaultDescription
tabsrequiredPageTabsItem[]—The pages, in the order to draw them.
currentrequiredstring—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.
labelstring | undefined—The navigation's accessible name. "Pages" by default; name what the pages are of ("Project").
classNamestring | undefined—Classes for the navigation, merged with Candor's.

Strings

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

KeyEnglish
labelPages

Motion

MomentFamilyWhat it does
tinttintA 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.

Types

PageTabsItem
One page `PageTabs` links to.
{ /** 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; }