# Elements: Navigation

> Plainkit 0.28.0. Generated by scripts/build-skills.mjs from core/dist/elements/api.json. Do not edit: change the source and run the generator.

How to read these tables: see `elements-index.md`. Each element shows its first example; the others are in `references/full/examples-navigation.md` (kept apart so the group files stay short).

## `pk-app-bar-search`

**App bar search** (Navigation). A search field for a shell's header row: a pill that fills the space it is given, results in a panel below it; a bare (borderless, plain-variant) icon button (--pk-button-border on the host gives it a border) that expands over the header row below the phone breakpoint or with compact. The host supplies items and reads pk-query and pk-select. Before it upgrades it reserves about 17.75rem of inline space, at most half the row, so the header does not jump; it has no default slot, so a no-JavaScript fallback (a plain search form) goes beside it and is hidden once the element is defined.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `label` | string | `"Search"` | The accessible name of the field and the expand button. |
| `placeholder` | string | `""` | Hint shown while empty. |
| `debounce` | number | `250` | Milliseconds to wait after the last keystroke before raising pk-query. |
| `compact` | boolean | `false` | Icon button at any width; expands over the header row. |
| `expanded` | boolean | `false` | Whether the field covers the header row (phone breakpoint, or compact). |
| `items` | json | `[]` | Results: { id, label, group?, sub?, thumbnail?, badge?, href? }[]. Adjacent items with the same group share a heading; the host sets the order. An item with an href (http, https, mailto, tel or a same-site path) is a real link: open in a new tab and the status-bar address work; a plain click still raises pk-select, and preventDefault on it keeps the host's own routing (uncancelled, the browser follows the link). An item with no id is a note (a group's "+12 more"): shown, never selectable. The host clears items while the query is too short. |
| `results-label` (property `resultsLabel`) | string | `"results"` | The word announced after the count to assistive technology ("5 results"); set it to localise. The no-results text is the empty slot. |
| `error` | string | `""` | A failed search: while set, the open results panel shows this message instead of the no-results message, so a failure never reads as nothing found, and a Retry button under it. Set it when your search throws (for example "Search is unavailable. Try again.") and clear it with the next result. |
| `retry-label` (property `retryLabel`) | string | `"Retry"` | The text of the Retry button shown with error; set it to localise. |

**Slots**

| Slot | Description |
|---|---|
| `footer` | An action pinned to the bottom of the results panel (a Full search link). Hidden when empty. |
| `empty` | The message shown when items is empty and the field has text. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-query` | `{ query: string }` | The field's text, debounce milliseconds after the last keystroke. The host fetches or filters and sets items in response; this element never searches itself. |
| `pk-retry` | `{ }` | Retry was pressed on a failed search. pk-query is raised again right after it with the text in the field, so a host that only handles pk-query retries without listening to this; it is for a host that wants to log or reset state. |
| `pk-select` | `{ item: object }` | A result was chosen (click or Enter). The host decides what happens; this element does not navigate. |
| `pk-submit` | `{ query: string }` | Enter was pressed with a non-empty query and no result highlighted: the host shows a full results page or submits a form. This element never navigates. |
| `pk-toggle` | `{ expanded: bool }` | expanded changed by itself (the expand or collapse button, Escape, the shell's drawer opening, a navigation). Not raised for a change the host made. |

**CSS parts**

| Part | Description |
|---|---|
| `expand` | The icon button (phone breakpoint, or compact). |
| `box` | The bordered field. |
| `control` | The field: a pk-input (type search) laid into the pill with no box of its own. Its search icon is exported as box-icon. |
| `collapse` | The close button while expanded. |
| `popup` | The results panel. |
| `empty` | The no-results message. |
| `error` | The failed-search message (the error prop). |
| `retry` | The Retry button (a pk-button) shown under the failed-search message. |
| `status` | A visually hidden polite live region announcing the result count. |
| `footer` | The footer slot's container. |
| `option` | One result row (dynamic). |
| `thumb` | A result's thumbnail (dynamic; shown when the item has one). |
| `badge` | A result's badge text (dynamic; shown when the item has one). |
| `box-icon` | The field's icon. |
| `expand-icon` | The expand button's icon (the icon part of its pk-button, exported). |
| `collapse-icon` | The close button's icon (the icon part of its pk-button, exported). |
| `row` | A result row's label and subtitle column (dynamic). |
| `row-label` | A result's label text (dynamic). |
| `row-sub` | A result's subtitle text (dynamic; shown when the item has one). |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-app-bar-search-width` | `34rem` | Maximum width of the field. |
| `--pk-app-bar-search-align` | `auto` | The field's inline margin in a flex row such as the shell header: auto centres it in the free space between the nav toggle and the actions; 0 auto keeps it at the start and auto 0 pushes it to the end. Not applied to the icon button (compact, or a phone). |
| `--pk-app-bar-search-radius` | `var(--pk-control-radius, var(--radius-sm))` | Corner radius of the field; var(--radius-pill) for a pill. |
| `--pk-control-radius` | `var(--radius-sm)` | The shared control radius. |
| `--pk-app-bar-search-popup-width` | `24rem` | Minimum width of the results panel, which may be wider than the field (never wider than the viewport). |

**Accessibility.** ARIA 1.2 combobox with aria-activedescendant (focus stays on the input). The input is a pk-input in this element's shadow tree and the results are in this one, so aria-controls and aria-activedescendant are set as element references (pk-input listbox; Chrome 135, Safari 16.4, Firefox 136: in an older browser they do nothing, the role, name and expanded state still apply). The highlighted result is the one option with aria-selected="true". Arrow keys move, Enter picks (a result with an href is a link and Enter follows it unless pk-select is cancelled), Escape closes the results, then collapses the field. The buttons are 44px touch targets. Enter with no highlighted result raises pk-submit. A visually hidden polite live region announces the result count after each update.

Example: In an app shell header

```html
<pk-app-shell><pk-app-bar-search slot="header" placeholder="Search…"></pk-app-bar-search></pk-app-shell>
```

## `pk-back-to-top`

**Back to top** (Navigation). A floating button that appears once the reader is well down the page and scrolls the page (or a named scroller) back to the top, smoothly unless reduced motion is on.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `for` | string | `""` | Scroll container (default: the window). |
| `threshold` | number | `400` | Scroll distance in px past which the button appears; below 0 it is always shown (previews, short pages). |
| `label` | string | `"Back to top"` | Accessible name |
| `visible` | boolean | `false` | Set by the element. |

**Slots**

| Slot | Description |
|---|---|
| (default) | Label. |

**CSS parts**

| Part | Description |
|---|---|
| `button` | The button |
| `icon` | Kind icon |

**Accessibility.** A real button with a text label; after scrolling, focus moves to the top of the scroller so keyboard users are not left at the bottom.

Example: Back to top

```html
<pk-back-to-top label="Back to top" threshold="-1"></pk-back-to-top>
```

## `pk-breadcrumb`

**Breadcrumb** (Navigation). The path to the current page as a trail of links with chevrons; the middle collapses behind a "..." button when the trail is long, sooner on a phone.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `label` | string | `"Breadcrumb"` | Landmark name. |
| `max` | number | `4` | Crumbs shown before the middle folds (3 on a phone). |

**Slots**

| Slot | Description |
|---|---|
| (default) | a elements; the last one is the current page. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-expand` | `none` | The fold button revealed the hidden crumbs. |

**CSS parts**

| Part | Description |
|---|---|
| `nav` | The nav landmark |
| `list` | The list of items |
| `item` | Crumb list item |
| `fold` | Fold item |
| `more` | Fold button |

**Writes to host nodes**

| Target | Writes | Why |
|---|---|---|
| the slotted crumbs | `data-folded` | Toggled on the crumbs the fold hides (the styling hook that collapses them); removed for the crumbs shown again. Kept when the element disconnects. |
| the last crumb | `aria-current` | Set to page on the last crumb when no crumb carries aria-current already (the host may mark the current page itself). Not removed if the crumbs change later. Kept when the element disconnects. |

**Accessibility.** A nav landmark labelled Breadcrumb; the current page has aria-current="page"; the fold button says what it reveals and reflects aria-expanded. When the last crumb is also doing duty as the page's only title (pk-page-header's record variant with no heading set), pk-breadcrumb does not add a heading role on its own — see pk-page-header's a11y notes for the composition that adds role="heading" and aria-level to that crumb.

Example: Breadcrumb

```html
<pk-breadcrumb label="Breadcrumb"><a href="#">Home</a><a href="#">Products</a><a href="#">Books</a><a href="#">Item 12</a><a href="#" aria-current="page">Variants</a></pk-breadcrumb>
```

## `pk-link`

**Link** (Navigation). A router-aware text link: a real anchor in prose or a nav row, with href for an ordinary address or to for an app route the app router handles without a full page load.

**Props**

| Attribute | Type | Default | Values | Description |
|---|---|---|---|---|
| `href` | string | `""` |  | An external or same-document address. Navigates natively (middle and ctrl-click, the status-bar URL). Ignored when to is set. |
| `to` | string | `""` |  | A module-relative route. A plain click is intercepted and reported as a cancelable pk-navigate for the app router (js/router.js mountRouter, mounted with mountApp) to handle; a modified click (middle, ctrl, shift, alt), target or download, or a router that does not cancel the event, falls back to a normal navigation. |
| `navigate` | string | `""` |  | Who follows a to link: router (default, empty), host or none. |
| `target` | string | `""` |  | Where the link opens (for example _blank). _blank adds rel="noopener noreferrer" automatically and a visually hidden "(opens in a new tab)" after the label. |
| `rel` | string | `""` |  | The link relation. Defaults to noopener noreferrer when target is _blank. |
| `download` | string | `""` |  | Downloads the target instead of opening it, under the file name given (the bare attribute keeps the name from the URL). |
| `current` | boolean | `false` |  | Marks the link as the current page: sets aria-current="page" on the anchor. |
| `variant` | enum | `"inline"` | `inline` `muted` `plain` | inline is a coloured, underlined prose link; muted is a quiet reference (a nav row, a card footer); plain keeps the surrounding text colour. |

**Slots**

| Slot | Description |
|---|---|
| (default) | The link text. No decorative glyph belongs inside it (#315); put an icon before or after the element instead. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `click` | `native MouseEvent` | Any click on the link, href or to (bubbles from the inner anchor, #1747): a host closes a menu or flyout or records the click as the link navigates. It does not change the navigation: a modified click (ctrl, shift, cmd) still opens a new tab or window, and a middle click fires auxclick, not click. preventDefault in a script stops the navigation, as on a native link. |
| `pk-navigate` | `{ to: string, href: string }` | A plain click on a to link, before any navigation. Cancelable: the app router calls preventDefault and handles it; if nothing cancels the event pk-link falls back to a normal navigation to to. |

**Methods**

| Method | Description |
|---|---|
| `focus()` | Focuses the inner anchor. |

**CSS parts**

| Part | Description |
|---|---|
| `link` | The anchor element. |
| `tab` | The visually hidden "(opens in a new tab)" text, shown only when target is _blank. |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-link-color` | `the variant colour` | Text colour. |
| `--pk-link-color-hover` | `the variant hover colour` | Text colour on hover. |

**Accessibility.** A real <a> lives in the shadow root (exposed as a link, works with no JS), with a visible focus ring. current sets aria-current="page" for the current page in a trail or nav list. An external link that opens in a new tab (target set to _blank) adds a visually hidden "(opens in a new tab)" after the label, so its accessible name still says where it leads without a decorative icon inside the link box.

Example: Inline prose link

```html
<p>Read the <pk-link href="/guides/routing">routing guide</pk-link> before wiring the app shell.</p>
```

## `pk-nav-item`

**Nav item** (Navigation). A row of a side nav: a link, or a branch that folds its children; icon, badge, active bar and disabled state. Also works as a horizontal bar item inside pk-navbar (it detects the ancestry itself).

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `href` | string | `""` | Makes it a link; omit for a branch. |
| `current` | boolean | `false` | The current setting |
| `disabled` | boolean | `false` | Prevents interaction |
| `expanded` | boolean | `false` | Branch open. |
| `rail` | boolean | `false` | Set by the nav in icon-rail mode, on the top-level rows only: a child inside an open flyout keeps its label. |
| `horizontal` | boolean | `false` | Set automatically when the row sits inside pk-navbar: lays out as a horizontal bar item (bottom-bar current-page indicator) instead of a vertical tree row. Not meant to be set by hand. |
| `flyout` | boolean | `false` | In the icon rail, the branch is open as a flyout beside the rail. |
| `group` | boolean | `false` | A group title instead of a row: static muted text that introduces the rows below it. Not focusable, skipped by the arrow keys and hidden while the filter is used; in the icon rail it shows as a divider. |

**Slots**

| Slot | Description |
|---|---|
| (default) | Label. An author-written <a href> child here is the row's link: it works without JavaScript, the element styles it and never changes it (set aria-current on it and `current` on the row yourself). |
| `icon` | Icon. |
| `badge` | Count or tag. |
| `children` | Nested pk-nav-item. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-toggle` | `{ expanded: boolean }` | A branch folded or unfolded. |

**CSS parts**

| Part | Description |
|---|---|
| `link` | A link |
| `icon` | Kind icon |
| `label` | Label text |
| `badge` | Count or tag |
| `chevron` | Submenu or branch chevron |
| `sub` | Children of a branch (in the icon rail: the flyout, a group named by the branch) |
| `sub-title` | The branch name, shown as the flyout heading in the icon rail |

**Accessibility.** A link (or a button with aria-expanded for a branch); the current row has aria-current="page"; disabled rows are aria-disabled and skipped by arrow keys. A group title is plain text (role presentation), not in the tab order. In the icon rail, a branch whose flyout holds the current row gets a visual indicator on its own collapsed link (it keeps no aria-current itself: it is not the current page).

Example: Row states

```html
<div class="stack"><pk-nav-item href="#" current>Current</pk-nav-item><pk-nav-item href="#"><span slot="badge">3</span>With a badge</pk-nav-item><pk-nav-item href="#" disabled>Disabled</pk-nav-item><pk-nav-item expanded>Branch<pk-nav-item slot="children" href="#">Child</pk-nav-item></pk-nav-item><pk-nav-item group>Group title</pk-nav-item><pk-nav-item href="#">Under the title</pk-nav-item></div>
```

## `pk-navbar`

**Navbar** (Navigation). The top navigation: brand, links, dropdown and mega menus, right-aligned actions, sticky option, and a hamburger that folds the links below 1024px.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `label` | string | `"Main"` | Accessible name |
| `open` | boolean | `false` | Links unfolded (small screens). |
| `sticky` | boolean | `false` | The sticky setting |
| `no-fold` (property `noFold`) | boolean | `false` | For a host that owns the menu on narrow screens (the app shell's drawer): below 1024px the links stay hidden and the hamburger is not shown, and the actions stay at the end of one non-wrapping row while the brand shortens first. |

**Slots**

| Slot | Description |
|---|---|
| `brand` | Brand link. |
| (default) | Links, pk-dropdown menus, or pk-nav-item rows (it detects the navbar itself, no attribute to set). |
| `actions` | Right-aligned controls. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-toggle` | `{ open: boolean }` | Hamburger toggled. |

**CSS parts**

| Part | Description |
|---|---|
| `nav` | The nav landmark |
| `brand` | Brand area |
| `toggle` | Hamburger button |
| `bars` | Hamburger bars |
| `links` | Link group |
| `actions` | Action buttons |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-navbar-padding` | `var(--space-4)` | The bar's inline padding. A shell that already pads its header row sets it to 0 so the brand and the actions meet the header's own edges. |

**Accessibility.** A nav landmark; the hamburger is a button with aria-expanded controlling the links; the current link has aria-current="page". A theme's glass surface tokens tint the bar (--surface-tint) and frost a sticky one (--surface-blur); with reduced transparency or more contrast asked for, or without backdrop-filter support, the bar is solid --color-panel.

Example: Navbar with a menu and actions

```html
<pk-navbar label="Main"><a slot="brand" href="#">Brand</a><a href="#" aria-current="page">Dashboard</a><a href="#">Orders</a><pk-dropdown><pk-button slot="trigger" variant="ghost">More</pk-button><pk-menu-item href="#">Customers</pk-menu-item><pk-menu-item href="#">Reports</pk-menu-item></pk-dropdown><pk-button slot="actions" variant="ghost" size="mini">Account</pk-button></pk-navbar>
```

## `pk-pager`

**Pager** (Navigation). Page-level previous and next links between sibling pages, with an optional centre note ("Step 2 of 5"); table pagination is a separate control.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `label` | string | `"Page navigation"` | Accessible name |

**Slots**

| Slot | Description |
|---|---|
| `prev` | The previous-page link. |
| `next` | The next-page link. |
| (default) | Centre note. |

**CSS parts**

| Part | Description |
|---|---|
| `nav` | The nav landmark |
| `prev` | Previous control |
| `note` | note |
| `next` | Next control |

**Accessibility.** A nav landmark; links carry rel="prev" and rel="next" when the author sets them, and say where they go.

Example: Previous and next page

```html
<pk-pager label="Page navigation"><a slot="prev" href="#">Previous: Setup</a><span>2 of 5</span><a slot="next" href="#">Next: Usage</a></pk-pager>
```

## `pk-pagination`

**Pagination** (Navigation). Page navigation: numbered with ellipses, a range summary, a page-size select, a simple previous and next form, or load more. On a phone the numbers collapse to previous, Page N of M and next.

**Props**

| Attribute | Type | Default | Values | Description |
|---|---|---|---|---|
| `page` | number | `1` |  | Current page, 1-based. |
| `pages` | number | `0` |  | Page count; 0 derives it from total and pageSize. |
| `total` | number | `0` |  | Item count; enables the range summary. |
| `page-size` (property `pageSize`) | number | `25` |  | Items per page. |
| `sizes` | json | `[]` |  | Options for the page-size select, for example [10, 25, 50, 100]; empty hides the select. |
| `siblings` | number | `1` |  | Pages either side of the current one. |
| `boundary` | number | `1` |  | Pages always shown at each end. |
| `mode` | enum | `"numbered"` | `numbered` `simple` `load-more` | Layout. |
| `label` | string | `"Pagination"` |  | Accessible name of the nav. |
| `loading` | boolean | `false` |  | Load-more busy state. |
| `edges` | boolean | `false` |  | Show First and Last buttons beside Previous and Next. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-page` | `{ page: number }` | A page was chosen. Cancel to keep the current page (a server-driven grid sets page itself). |
| `pk-page-size` | `{ pageSize: number }` | The page-size select changed. Cancel to keep the size. |
| `pk-load-more` | `none` | Load more was pressed. |

**Methods**

| Method | Description |
|---|---|
| `pageWindow(current, total, options)` | The numbers and gaps to show. |
| `pageRange(page, size, total)` | First and last item on a page. |

**CSS parts**

| Part | Description |
|---|---|
| `nav` | The nav. |
| `summary` | The range text, such as 21-30 of 95. |
| `size` | The page-size label. |
| `size-select` | The page-size select. |
| `pages` | The button row. |
| `prev` | Previous button. |
| `numbers` | Numbered buttons and ellipses. |
| `status` | Page N of M text. |
| `next` | Next button. |
| `more` | Load more button. |
| `first` | First page button (edges). |
| `last` | Last page button (edges). |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-pagination-button-bg` | `var(--color-input)` | Resting background of the page buttons. |
| `--pk-pagination-button-border` | `color-mix(in srgb, var(--color-muted) 85%, var(--color-border))` | Border colour of the page buttons. |
| `--pk-pagination-size` | `var(--space-8)` | Button size; the touch target on a phone. |

**Accessibility.** A nav landmark with a label; the current page has aria-current=page and every number button has a name ("Page 3"); previous and next are disabled at the ends; the ellipsis is decorative. Buttons are 44px on a phone.

Example: Numbered with summary and page size

```html
<pk-pagination page="3" total="290" page-size="25" sizes='[10,25,50,100]'></pk-pagination>
```

## `pk-scroll-progress`

**Scroll progress** (Navigation). A thin bar at the top of the screen that fills as the page (or a named scroller) is read.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `for` | string | `""` | Scroll container (default: the window). |

**CSS parts**

| Part | Description |
|---|---|
| `bar` | The bar |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-progress` | `0` | Scrolled fraction 0 to 1, set by the element |

**Accessibility.** Decorative (aria-hidden); the scroll position is already available to assistive tech.

Example: Reading progress bar

```html
<pk-scroll-progress></pk-scroll-progress>
```

## `pk-side-nav`

**Side nav** (Navigation). The app’s side navigation: brand, filter box, nested groups with arrow-key tree navigation, collapse to an icon rail (tooltips on rows, branches open as flyouts), persisted state, and an off-canvas drawer with backdrop at 1024px and below.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `label` | string | `"Main"` | Accessible name |
| `collapsed` | boolean | `false` | Icon rail. |
| `open` | boolean | `false` | Drawer open (phones and tablets). |
| `filterable` | boolean | `false` | Show the filter box. |
| `persist` | string | `""` | Browser storage key for collapsed and open branches. |
| `current-path` (property `currentPath`) | string | `""` | The host's current URL path. When set, resolves the deepest-matching pk-nav-item (by href) and sets its `current`, clearing every other row; see resolveActiveRoute in nav-logic.js for the algorithm. |
| `auto-expand-active` (property `autoExpandActive`) | boolean | `false` | With current-path, also opens every ancestor branch of the active row and collapses every other branch. |
| `allow-multiple` (property `allowMultiple`) | boolean | `false` | Let several branches of one level stay open. By default opening a branch collapses its expanded siblings. |

**Slots**

| Slot | Description |
|---|---|
| `brand` | Brand link. |
| (default) | pk-nav-item children; a pk-nav-item with the group attribute is a group title. |
| `footer` | Pinned bottom items. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-nav-toggle` | `{ collapsed: boolean }` | Rail toggled by the user, or restored from the saved state (persist) on connect. |
| `pk-open` | `none` | Drawer opened. |
| `pk-close` | `{ reason: string }` | Drawer closed. |

**Methods**

| Method | Description |
|---|---|
| `show()` | Opens it |
| `hide()` | Closes it |
| `toggleCollapsed()` | Toggles the icon rail |

**CSS parts**

| Part | Description |
|---|---|
| `nav` | The nav landmark |
| `brand` | Brand area |
| `collapse` | Collapse toggle |
| `filter` | Filter row |
| `filter-input` | Filter field |
| `scroll` | The scrolling list |
| `foot` | Footer area |
| `backdrop` | Dimmed backdrop |

**Writes to host nodes**

| Target | Writes | Why |
|---|---|---|
| the pk-nav-item children (at any depth) | `rail`, `expanded`, `flyout`, `hidden` | rail is pushed down to the top-level rows (children in a flyout keep their labels) whenever the collapsed state or the breakpoint changes (an item cannot know it); the filter box sets hidden on rows that do not match and expanded on the parents of a match, and restores the expanded state when the box is cleared; keyboard navigation sets expanded or flyout on the focused row. The one pair of elements whose props are written by the parent. Kept when the element disconnects. |

**Accessibility.** A nav landmark; branches are buttons with aria-expanded; Up/Down move between rows, Right opens or enters a branch, Left folds or goes up, Home/End jump; the filter announces its result count; the drawer hides from assistive tech when closed and returns focus to its opener.

Example: Side nav with a branch, filter and icon rail

```html
<pk-side-nav label="Main" filterable persist="pk-sample-nav"><a slot="brand" href="#">Brand</a><pk-nav-item href="#" current>Dashboard</pk-nav-item><pk-nav-item href="#"><span slot="badge">12</span>Orders</pk-nav-item><pk-nav-item expanded>Products<pk-nav-item slot="children" href="#">All products</pk-nav-item><pk-nav-item slot="children" href="#">Drafts</pk-nav-item></pk-nav-item><pk-nav-item href="#" disabled>Finance (soon)</pk-nav-item><pk-nav-item slot="footer" href="#">Settings</pk-nav-item></pk-side-nav>
```

## `pk-skip-link`

**Skip link** (Navigation). The first focusable element on a page: invisible until it takes focus, then a button that jumps past the navigation to the main content.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `href` | string | `"#main"` | Same-page target. |

**Slots**

| Slot | Description |
|---|---|
| (default) | Label (default "Skip to content"). |

**CSS parts**

| Part | Description |
|---|---|
| `link` | A link |

**Writes to host nodes**

| Target | Writes | Why |
|---|---|---|
| the page element the href points at | `tabindex` | tabindex=-1 when the target has none, so the click can move focus there. Not removed. Kept when the element disconnects. |

**Accessibility.** Target must be a fragment on the same page; the element makes the target focusable so focus really moves.

Example: Skip link (Tab to reveal it)

```html
<pk-skip-link href="#skip-demo">Skip to content</pk-skip-link><p id="skip-demo" tabindex="-1">Main content starts here. Press Tab to see the link above.</p>
```

## `pk-step`

**Step** (Navigation). One step of a stepper: a marker (number, tick or !), a title, an optional description and the connector to the next step.

**Props**

| Attribute | Type | Default | Values | Description |
|---|---|---|---|---|
| `heading` | string | `""` |  | The title setting |
| `description` | string | `""` |  | The description setting |
| `state` | enum | `"todo"` | `todo` `active` `done` `error` | Set by the stepper. |
| `index` | number | `0` |  | One-based, set by the stepper. |
| `disabled` | boolean | `false` |  | Prevents interaction |
| `last` | boolean | `false` |  | Set by the stepper: the last step draws no connector. |
| `clickable` | boolean | `false` |  | Set by the stepper: the step can be entered. |
| `orientation` | enum | `"horizontal"` | `horizontal` `vertical` | Set by the stepper. |

**Slots**

| Slot | Description |
|---|---|
| (default) | Title (overrides the title prop). |
| `description` | Description. |

**CSS parts**

| Part | Description |
|---|---|
| `marker` | Numbered marker |
| `text` | Title and description |
| `title` | Heading text |
| `description` | Secondary text |
| `connector` | Line to the next step |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-step-c` | `--color-border` | State colour |

**Accessibility.** The stepper gives it role listitem and the descriptive label; state is also in aria-current and aria-disabled.

Example: Step states

```html
<div class="stack"><pk-step heading="Done" index="1" state="done"></pk-step><pk-step heading="Current" index="2" state="active" description="You are here"></pk-step><pk-step heading="To do" index="3" state="todo"></pk-step><pk-step heading="Error" index="4" state="error" description="Fix this"></pk-step></div>
```

## `pk-stepper`

**Stepper** (Navigation). A wizard header: numbered steps with done, current, upcoming and error states, horizontal or vertical, optionally clickable (linear: you can go back, not skip ahead).

**Props**

| Attribute | Type | Default | Values | Description |
|---|---|---|---|---|
| `current` | number | `0` |  | Zero-based current step. |
| `orientation` | enum | `"horizontal"` | `horizontal` `vertical` | The orientation setting |
| `clickable` | boolean | `false` |  | Steps can be entered by click or key. |
| `free` | boolean | `false` |  | Allow skipping ahead of the furthest step reached (non-linear). |
| `errors` | string | `""` |  | Comma-separated step indexes with an error. |
| `label` | string | `"Progress"` |  | Accessible name. |

**Slots**

| Slot | Description |
|---|---|
| (default) | pk-step children. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-step-change` | `{ index: number, previous: number }` | A step was entered; cancelable. |

**Methods**

| Method | Description |
|---|---|
| `goTo(index)` | See the description |
| `next()` | Next step |
| `previous()` | Previous step |

**CSS parts**

| Part | Description |
|---|---|
| `list` | The list of items |

**Writes to host nodes**

| Target | Writes | Why |
|---|---|---|
| the pk-step children | `state`, `index`, `last`, `clickable`, `orientation`, `disabled`, `tabindex`, `aria()` | The parent derives each step state, number, last flag, orientation, clickability and (in guided mode) disabled from the current step and pushes them down, with roving tabindex and role, label, current and disabled through the step ElementInternals (aria()). Refreshed on every change; none is undone on disconnect. |

**Accessibility.** A labelled list; each step announces "Step 2 of 4: Pricing, current"; the current step has aria-current="step"; clickable steps are buttons reachable by Tab and by arrow keys.

Example: Horizontal stepper (clickable, linear)

```html
<pk-stepper label="Create product" current="1" clickable><pk-step heading="Basics" description="Name and type"></pk-step><pk-step heading="Pricing" description="Cost and price"></pk-step><pk-step heading="Images"></pk-step><pk-step heading="Review"></pk-step></pk-stepper>
```

## `pk-tab`

**Tab** (Navigation). One tab of an pk-tabs. Place it inside pk-tabs; it selects its panel by value. It can show a count, a close button, and appear only on a phone or only on a desktop.

**Props**

| Attribute | Type | Default | Values | Description |
|---|---|---|---|---|
| `value` | string | `""` |  | The key that pairs this tab with the pk-tab-panel of the same value. |
| `selected` | boolean | `false` |  | Set by the surrounding pk-tabs. |
| `disabled` | boolean | `false` |  | Cannot be selected or focused by arrow keys. |
| `count` | string | `""` |  | A badge shown in muted parentheses after the label (a queue count); empty shows none. |
| `closable` | boolean | `false` |  | Shows a close button, keyboard reachable on the selected tab; pk-tab-close is raised when it is activated and the page removes the tab. |
| `only` | enum | `"all"` | `all` `phone` `desktop` | all shows the tab everywhere; phone only at phone width (640px and below); desktop hides it at phone width. |

**Slots**

| Slot | Description |
|---|---|
| (default) | The tab label. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-tab-close` | `{ value }` | The close button of a closable tab was clicked. |

**CSS parts**

| Part | Description |
|---|---|
| `label` | The label wrapper. |
| `count` | The count badge. |
| `close` | The close button. |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-tab-fg` | `var(--color-muted)` | Text colour of an unselected tab. |

**Accessibility.** Exposes role=tab, aria-selected and aria-disabled through ElementInternals. pk-tabs manages tabindex (roving) and aria-controls. The close button is labelled and reachable with Tab from the selected tab (the other tabs' buttons stay out of the tab order, like the tabs themselves); Enter or Space closes.

Example: Inside pk-tabs

```html
<pk-tabs value="a">
  <pk-tab value="a" count="12">Pending</pk-tab>
  <pk-tab value="b" closable>Draft</pk-tab>
  <pk-tab value="c" disabled>Locked</pk-tab>
  <pk-tab-panel value="a">Pending content</pk-tab-panel>
</pk-tabs>
```

## `pk-tab-panel`

**Tab panel** (Navigation). The content of one tab; shown while its tab is selected.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `value` | string | `""` | The key that pairs this panel with the pk-tab of the same value. |
| `selected` | boolean | `false` | Set by the surrounding pk-tabs. |

**Slots**

| Slot | Description |
|---|---|
| (default) | The panel content. |

**Accessibility.** Exposes role=tabpanel; pk-tabs sets aria-labelledby to its tab.

Example: Two panels

```html
<pk-tabs value="a">
  <pk-tab value="a">One</pk-tab>
  <pk-tab value="b">Two</pk-tab>
  <pk-tab-panel value="a">First</pk-tab-panel>
  <pk-tab-panel value="b">Second</pk-tab-panel>
</pk-tabs>
```

## `pk-tabs`

**Tabs** (Navigation). A tab strip with optional panels. Children: pk-tab and pk-tab-panel paired by value. Arrow keys, Home and End move between tabs (roving tabindex). It can show no tab as selected, and hold trailing content after the last tab. When the tabs outgrow the width it slides with edge fades (overflow="scroll", the default), collapses the tabs that do not fit behind a "..." menu button (overflow="menu"), or wraps onto more rows (overflow="wrap").

**Props**

| Attribute | Type | Default | Values | Description |
|---|---|---|---|---|
| `value` | string | `""` |  | The value of the selected tab. Empty selects the first enabled tab. |
| `activation` | enum | `"auto"` | `auto` `manual` | auto selects a tab when arrow keys focus it; manual moves focus and waits for Enter or Space. |
| `none-active` (property `noneActive`) | boolean | `false` |  | No tab is shown as selected, and choosing any tab (even the last one chosen) raises pk-tab-change. For a page whose cross-cutting view sits above its tabs. |
| `overflow` | enum | `"scroll"` | `scroll` `menu` `wrap` | How the strip behaves when the tabs outgrow its width. scroll (the default): one row that slides (touch swipe, mouse wheel, trackpad, drag); a fade marks the edge that has more tabs (it follows the scroll, the tab list and the width, and mirrors in right-to-left), and moving to a tab with the arrow keys, Home, End or a click scrolls it fully into view, clear of the fade, smoothly unless the reader prefers reduced motion. menu: one row, nothing slides; tabs that stop fitting are hidden behind a trailing "..." button (a menu button) whose menu lists them, so a hidden tab is still one click or Enter away and stays selectable; the selected tab is always pinned visible, even when it would otherwise be the one to overflow. wrap: the tabs wrap onto more rows instead of scrolling or collapsing; the explicit opt-in for what used to be the only behavior. |
| `tab-param` (property `tabParam`) | string | `""` |  | A query parameter name ("tab") that keeps the selected tab in the address: the first sync selects the tab the address names (it wins over value; an unknown or disabled one is ignored), and a later change is written with history.replaceState (other parameters and the hash kept, no history entry, no router navigation). Not used with none-active. |

**Slots**

| Slot | Description |
|---|---|
| `tab` | pk-tab children (they assign themselves to this slot). |
| `panel` | pk-tab-panel children (they assign themselves to this slot). |
| `trailing` | Content inside the strip after the last tab: a hint, links or buttons. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-tab-change` | `{ value, previous, fallback? }` | A tab was chosen. Cancelable: preventDefault keeps the previous tab. Also raised, not cancelable, when the value falls back to the first enabled tab (empty or no matching tab), so the host learns the value the element settled on; that one carries fallback: true, so a host that only reacts to a choice the user made can skip it. |

**CSS parts**

| Part | Description |
|---|---|
| `list` | The tab strip. |
| `panels` | The panel area. |
| `more` | The overflow menu (a pk-dropdown), present only for overflow="menu". |
| `more-trigger` | The trailing "..." button that opens the overflow menu. |

**Writes to host nodes**

| Target | Writes | Why |
|---|---|---|
| pk-tab children | `selected`, `tabindex`, `id`, `aria-controls`, `data-overflow-hidden` | selected and tabindex (roving) are set on every sync from the value; id is set only when the tab has none; aria-controls names the paired panel. selected is a reflected prop of pk-tab. data-overflow-hidden is set only for overflow="menu", on the tabs that do not fit (never the selected one); it is the styling hook that hides them (display: none), which is what keeps them out of the roving tab order automatically. Kept when the element disconnects. |
| pk-tab-panel children | `selected`, `tabindex`, `id`, `aria-labelledby` | selected and tabindex follow the value; id is set only when the panel has none; aria-labelledby names the paired tab. Both live in the same tree, so the ids resolve. Kept when the element disconnects. |

**Accessibility.** The strip has role=tablist; tabs and panels are related with aria-controls and aria-labelledby set on the light-DOM children (both live in the same tree, so the ids resolve). Roving tabindex: only the selected tab is in the Tab order (every tab when none is active); arrow keys move focus among the visible, enabled tabs (Home/End jump to the first/last of those) and wrap at the ends; they do not reach into the overflow menu. For overflow="menu" the "..." trigger is a normal tab stop after the tablist (menubutton pattern: aria-haspopup="menu", aria-expanded, from pk-dropdown) with its own aria-label naming how many tabs it hides; ArrowDown/ArrowUp on it opens the menu and moves focus to its first/last item (pk-dropdown), and choosing a hidden tab there activates it (pk-select) and moves focus to it in the strip. The selected tab is always pinned visible and is never one of the ones collapsed into the menu.

Example: Tabs with panels

```html
<pk-tabs value="details">
  <pk-tab value="details">Details</pk-tab>
  <pk-tab value="history">History</pk-tab>
  <pk-tab-panel value="details">Item details.</pk-tab-panel>
  <pk-tab-panel value="history">Item history.</pk-tab-panel>
</pk-tabs>
```

## `pk-toc`

**On this page** (Navigation). An anchor list of the headings inside a target element with scroll-spy: the heading in view is marked as you read; works with the window or an inner scroller such as the shell body.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `for` | string | `""` | The element whose headings are listed. |
| `levels` | string | `"h2,h3"` | Which headings to list. |
| `offset` | number | `80` | Distance from the top a heading must reach to count as current. |
| `scroller` | string | `""` | Scroll container (default: the window). |
| `heading` | string | `""` | The title setting |
| `label` | string | `"On this page"` | Accessible name |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-section-change` | `{ id: string }` | The current heading changed. |

**Methods**

| Method | Description |
|---|---|
| `refresh()` | Re-reads the headings |

**CSS parts**

| Part | Description |
|---|---|
| `nav` | The nav landmark |
| `title` | Heading text |
| `list` | The list of heading links (built inside it) |

**Writes to host nodes**

| Target | Writes | Why |
|---|---|---|
| the headings elsewhere in the document that the for selector matches | `id` | A heading without an id gets a slug of its text, so the link and the scroll spy have something to point at. Only ever added, never changed or removed. Kept when the element disconnects. |

**Accessibility.** A nav landmark; the current link has aria-current="location"; links are real anchors, so the browser handles focus and history.

Example: On this page

```html
<pk-grid columns="2" min="16rem"><article id="toc-demo"><h2 id="toc-a">Overview</h2><p>What it is.</p><h2 id="toc-b">Usage</h2><p>How to use it.</p><h3 id="toc-c">Options</h3><p>The knobs.</p></article><pk-toc for="#toc-demo" heading="On this page" label="On this page"></pk-toc></pk-grid>
```

## `pk-tree`

**Tree** (Navigation). An expandable hierarchy of tree items with roving focus, single selection and type-ahead, following the WAI-ARIA tree pattern.

**Props**

| Attribute | Type | Default | Values | Description |
|---|---|---|---|---|
| `label` | string | `""` |  | Accessible name. |
| `selection` | enum | `"single"` | `single` `none` | Selection mode. |
| `wrap-labels` (property `wrapLabels`) | boolean | `false` |  | Let a long item label wrap onto several lines instead of one line cut off with an ellipsis (the item hook is --pk-tree-item-label-white-space). A wrapped row keeps a small padding above and below its label (--pk-tree-item-row-padding-block). |
| `navigate` | string | `""` |  | The navigate mode of items with an href that set none: router (default), host (the host navigates) or none. |
| `value` | string | `""` |  | The value (or label) of the selected item. |

**Slots**

| Slot | Description |
|---|---|
| (default) | Root pk-tree-item children. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-select` | `{ id: string }` | An item was chosen (bubbles from the item). |
| `pk-toggle` | `{ id: string, expanded: bool }` | A branch opened or closed (bubbles from the item). |

**Methods**

| Method | Description |
|---|---|
| `flattenTree(nodes)` | Visible nodes with level and parent. |
| `treeKey(items, index, key)` | What a key does. |
| `typeahead(items, index, text)` | Next matching label. |

**Writes to host nodes**

| Target | Writes | Why |
|---|---|---|
| the pk-tree-item descendants | `tabindex`, `selected`, `aria()` | Roving tabindex, the selected flag from the tree value, and role, level, set size, position, expanded and selected pushed through the item ElementInternals (aria(), not attributes). Refreshed on every update; none is undone on disconnect. |

**Accessibility.** role=tree with treeitem, aria-expanded, aria-selected, aria-level, aria-setsize and aria-posinset. One tab stop; arrows and Home and End move; Right and Left expand and collapse or move to the child or parent; Enter and Space select; typing a letter jumps to the next label starting with it.

Example: Categories

```html
<pk-tree label="Categories">
  <pk-tree-item label="Books" expanded>
    <pk-tree-item label="Green"></pk-tree-item>
    <pk-tree-item label="DC"></pk-tree-item>
  </pk-tree-item>
  <pk-tree-item label="Cards"></pk-tree-item>
</pk-tree>
```

## `pk-tree-item`

**Tree item** (Navigation). One node of a tree: a toggle, a label and nested child items.

**Props**

| Attribute | Type | Default | Description |
|---|---|---|---|
| `label` | string | `""` | The text. |
| `value` | string | `""` | The value reported in events; defaults to the label. |
| `expanded` | boolean | `false` | Children visible. |
| `selected` | boolean | `false` | The chosen item. |
| `href` | string | `""` | Makes the label a real link (middle click, copy link, status-bar URL). A plain click selects the row and is reported as a cancelable pk-navigate; a modified click navigates natively. |
| `navigate` | string | `""` | Who follows the href on a click: router (default; cancelable pk-navigate), host (pk-navigate only notifies) or none (the browser follows). Empty takes the pk-tree's. |
| `disabled` | boolean | `false` | Not selectable. |
| `expandable` | boolean | `false` | Set by the element when it has child items. |

**Slots**

| Slot | Description |
|---|---|
| (default) | Child pk-tree-item elements. |
| `label` | A rich label; overrides label. |
| `icon` | An icon before the label. |

**Events**

| Event | Detail | Description |
|---|---|---|
| `pk-select` | `{ id: string }` | The row was activated. |
| `pk-navigate` | `{ to: string, href: string }` | A plain click on a row with href, before any navigation. Cancelable: the app router calls preventDefault and handles it; if nothing cancels it the row navigates normally. |
| `pk-toggle` | `{ id: string, expanded: bool }` | The branch opened or closed. |

**Methods**

| Method | Description |
|---|---|
| `setExpanded(value)` | Open or close the branch and fire pk-toggle. |
| `select()` | Fire pk-select. |

**CSS parts**

| Part | Description |
|---|---|
| `row` | The clickable row. |
| `toggle` | The chevron. |
| `label` | The label. |
| `children` | The nested group. |

**CSS custom properties**

| Property | Default | Description |
|---|---|---|
| `--pk-tree-item-row-padding-block` |  | Block padding of an item row (pk-tree wrap-labels sets it to --space-1). |
| `--pk-tree-item-label-white-space` |  | White-space of the label: nowrap (default, one line with an ellipsis) or normal; pk-tree wrap-labels sets normal. |
| `--pk-tree-item-label-overflow-wrap` |  | Overflow-wrap of the label; pk-tree wrap-labels sets anywhere. |
| `--pk-tree-item-indent` | `var(--space-5)` | Indent per level. |
| `--pk-tree-item-level` | `1` | Nesting depth; set by the element. |

**Accessibility.** Roles, levels, set sizes and states are set through ElementInternals by the tree. The host is the roving tab stop and shows a focus ring on the row, which is 44px tall on a phone.

Example: Leaf and branch

```html
<pk-tree-item label="Puzzles" selected></pk-tree-item>
```
