# xpl-top-nav-item

## Behavior

- **Plain item:** **`label`** / **`icon`** (or **`slot="leading-icon"`**), default slot, or **`href`** link. Renders a native **`<button type="button">`** (or **`<a href>`** in link mode) and fires **`navItemClick`** (**`detail`**: **`MouseEvent`**) on click and **Enter** / **Space** — the native button handles keyboard activation, and link-mode **Space** maps to a programmatic **`.click()`**.
- **Dropdown trigger:** Put content in **`slot="dropdown"`** (e.g. `<xpl-top-nav-menu>`). Shows an auto **`chevron-down`** (unless **`slot="trailing-icon"`** is provided) and toggles **`.xpl-top-nav-item__dropdown`** in the host light DOM — **not** **`xpl-dropdown`** / **`xpl-popover`**; use those for standalone floating menus (e.g. account in top-nav stories). **`navItemClick`** is not emitted. See **`TECH_DEBT.md`** for tradeoffs.
- **Disclosure ARIA:** A dropdown trigger's inner control carries **`aria-expanded`** (open state), **`aria-haspopup="true"`**, and **`aria-controls`** pointing at the **`.xpl-top-nav-item__dropdown`** panel's generated id. Plain items omit all three. **`aria-haspopup`** is the generic **`true`** (disclosure), not **`menu`** — the panel (**`xpl-top-nav-menu`**) is a **`role="list"`** of link rows navigated by **Tab**, not an ARIA menu with arrow-key roving.
- **Menu row:** The same component reused inside **`xpl-nav-header-menu`** / **`xpl-top-nav-menu`** — add **`description`** for a two-line row, and **`slot="trailing-icon"`** for a functional icon (e.g. `external-link` for "opens in new window").
- **Open API:** **`open`** (controlled/uncontrolled, reflected) + **`openChange`** event + **`openMenu()`** / **`closeMenu()`** / **`toggleMenu()`** methods — all equivalent ways to drive the dropdown panel.
- **`openMode`:** **`click`** (default) or **`hover`** — hover only activates on hover-capable, fine-pointer, **`md`**-and-up devices; keyboard (Enter/Space) always toggles regardless.
- **One open dropdown:** Outside click uses **`ownerDocument`** capture; siblings close via internal **`CustomEvent`** (reliable with JSDOM / **`stopPropagation`**).
- **Escape:** Document **capture** **`keydown`** — **`preventDefault`** / **`stopPropagation`** so parent shells (**`xpl-top-nav`** mobile drawer, **`xpl-popover`**, modal) do not also react on the same keypress, then returns focus to the trigger control. **`xpl-top-nav`** closes its drawer on **Escape** in **bubble** phase, after this handler runs.

## Host modifier classes (BEM)

| Class | Source |
| --- | --- |
| **`xpl-top-nav-item--selected`** | **`selected`** prop |
| **`xpl-top-nav-item--has-dropdown`** | **`slot="dropdown"`** content present |
| **`xpl-top-nav-item--dropdown-open`** | Dropdown panel is open (**`open`** prop) |
| **`xpl-top-nav-item--active`** | **`active`** prop (or add the class on the host for edge cases) |
| **`xpl-top-nav-item--has-active-descendant`** | A descendant row inside **`slot="dropdown"`** is **`active`** / **`selected`** — accents the trigger like **`--active`**. Synced at connect / load / this item's own re-renders, not via a live descendant-class observer (see the JSDoc on **`hasActiveDescendant`** for why). |

## Slots

| Slot | Description |
| --- | --- |
| (default) | Custom trigger when **`label`** is omitted. |
| `leading-icon` | Illustrative icon before the label. Overrides the **`icon`** prop when present. |
| `trailing-icon` | Functional icon after the label. Overrides the auto **`chevron-down`** on a dropdown trigger. |
| `dropdown` | Panel content (e.g. `<xpl-top-nav-menu>…</xpl-top-nav-menu>`). |

<!-- Auto Generated Below -->


## Overview

Single nav row for **`xpl-top-nav`** / **`xpl-nav-header-menu`** / **`xpl-top-nav-menu`**. The host uses
**`role="listitem"`** when the row participates in a list: a **`role="list"`** parent, **`slot="left-aside"`** on
**`xpl-top-nav`** (list host is **`.xpl-top-nav__left-primary`** while slotted nodes stay light-DOM children of
**`xpl-top-nav`**), or default-slot rows under **`xpl-nav-header-menu`** / **`xpl-top-nav-menu`**. That flag is
kept in **`@State`** and refreshed after render plus via **`MutationObserver`** on the host (**`class`**, **`slot`**) and
on **`parentElement`** (**`role`**) or on **`.xpl-top-nav__left-primary`** (**`role`**) for **`slot="left-aside"`** under **`xpl-top-nav`**) so DOM context changes
without a full reconnect still re-sync. The internal
**`xpl-top-nav__brand`** row omits **`listitem`** (it sits in **`.xpl-top-nav__brand-bar`**, not **`.xpl-top-nav__left-primary`**),
as do **`slot="right-aside-mobile"`** rows (not a list). **`slot="dropdown"`** uses a local **`.xpl-top-nav-item__dropdown`**
panel (not **`xpl-dropdown`**); see **`TECH_DEBT.md`**.

**This component does triple duty:** a plain bar item, a dropdown trigger (**`slot="dropdown"`** present), and a
dropdown/menu **row** (nested inside **`xpl-nav-header-menu`** or **`xpl-top-nav-menu`**). The **`description`** prop
only applies in the row context — bar items and triggers do not show it in Figma.

**Default slot (no `label`):** do not put **`xpl-top-nav-item__inner`** or other **`xpl-top-nav-item__*`** classes on
custom trigger content — the component renders the inner wrapper and BEM elements.

**Theming:** The host always includes Foundation’s **`dark`** class so **`--xpl-*`** purpose tokens resolve to dark-mode
values even when the page root is light (consistent with **`xpl-top-nav`**).

## Properties

| Property        | Attribute         | Description                                                                                                                                                                                                                                                                                                                               | Type                 | Default     |
| --------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ----------- |
| `active`        | `active`          | When true, adds **`xpl-top-nav-item--active`** for persistent accent styling (e.g. link color on the bar, or chrome inside **`xpl-nav-header-menu`**). Separate from **`selected`**, which sets **`aria-current="page"`** and **`--selected`**. Use both when the design needs “current page” semantics plus the active visual treatment. | `boolean`            | `false`     |
| `description`   | `description`     | Optional description line shown below **`label`** (row context only, e.g. inside **`xpl-nav-header-menu`** / **`xpl-top-nav-menu`**). Ignored when **`label`** is not set.                                                                                                                                                                | `string`             | `undefined` |
| `href`          | `href`            | If set without **`slot="dropdown"`**, the inner control renders as a native **`<a href>`** (link mode).                                                                                                                                                                                                                                   | `string`             | `undefined` |
| `icon`          | `icon`            | Icon name from **`@xplortech/apollo-icons`** (shown before the label when **`label`** is set). Ignored when **`slot="leading-icon"`** content is present.                                                                                                                                                                                 | `string`             | `''`        |
| `itemAriaLabel` | `item-aria-label` | Accessible name for the inner control when there is no **`label`** and no default-slot text (e.g. icon-only). If omitted in that case, **`aria-label`** defaults to **`Navigation item`**. Ignored when **`label`** or default-slot content supplies a visible name.                                                                      | `string`             | `undefined` |
| `label`         | `label`           | Text label. If omitted, use the default slot for custom trigger content. Long labels use **`text-ellipsis`** on **`.xpl-top-nav-item__label`** (see `top-nav.css`); **`title`** is set to the full string for hover tooltips when the visual line clips.                                                                                  | `string`             | `undefined` |
| `open`          | `open`            | Whether the **`slot="dropdown"`** panel is open. Reflected to the **`open`** attribute so consumers can drive it (controlled) or read it back (uncontrolled, toggled internally on click / hover / keyboard activation). Prefer **`openMenu()`** / **`closeMenu()`** / **`toggleMenu()`** over setting this directly from event handlers. | `boolean`            | `false`     |
| `openMode`      | `open-mode`       | How the **`slot="dropdown"`** panel opens. **`hover`** is gated on **`TOP_NAV_ITEM_HOVER_OPEN_MQ`** (hover-capable, fine pointer, **`md`** and up) — below that, or on touch / coarse-pointer devices, **`hover`** falls back to click/tap. Keyboard activation (Enter / Space) always toggles regardless of **`openMode`**.              | `"click" \| "hover"` | `'click'`   |
| `selected`      | `selected`        | When true, sets **`aria-current="page"`** and selected styling.                                                                                                                                                                                                                                                                           | `boolean`            | `false`     |


## Events

| Event          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Type                      |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| `navItemClick` | Emitted for plain items (button or link) on pointer **click**, or when keyboard activation produces a **`click`**: the native **`<button>`** fires a real **`click`** on **Enter** / **Space**, a native **`<a href>`** on **Enter**, and **Space** on a link maps to a programmatic **`.click()`**. **`detail`** is always the real **`MouseEvent`** from that activation (keyboard-driven clicks have **`detail === 0`**). Not emitted when **`slot="dropdown"`** is used. | `CustomEvent<MouseEvent>` |
| `openChange`   | Emitted whenever **`open`** changes (click, hover, keyboard, Escape, outside click, or programmatic).                                                                                                                                                                                                                                                                                                                                                                        | `CustomEvent<boolean>`    |


## Methods

### `closeMenu() => Promise<void>`

No-op when there is no **`slot="dropdown"`** panel to open.

#### Returns

Type: `Promise<void>`



### `openMenu() => Promise<void>`

No-op when there is no **`slot="dropdown"`** panel to open.

#### Returns

Type: `Promise<void>`



### `toggleMenu() => Promise<void>`

No-op when there is no **`slot="dropdown"`** panel to open.

#### Returns

Type: `Promise<void>`




## Slots

| Slot              | Description                                                                                                                                                                                                         |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"dropdown"`      | Panel content (e.g. `<xpl-top-nav-menu>`). Presence of this slot makes the row a disclosure trigger.                                                                                                                |
| `"leading-icon"`  | Illustrative icon before the label. Overrides the **`icon`** prop when present.                                                                                                                                     |
| `"trailing-icon"` | Functional icon after the label (e.g. **`external-link`** for "opens in new window"). When **`slot="dropdown"`** is present and no **`trailing-icon`** content is slotted, an auto **`chevron-down`** renders here. |


## Dependencies

### Used by

 - [xpl-top-nav](..)
 - [xpl-top-nav-menu](../xpl-top-nav-menu)

### Depends on

- [xpl-icon](../../xpl-icon)

### Graph
```mermaid
graph TD;
  xpl-top-nav-item --> xpl-icon
  xpl-top-nav --> xpl-top-nav-item
  xpl-top-nav-menu --> xpl-top-nav-item
  style xpl-top-nav-item fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
