# Glide highlight

A box drawn behind a list of items (a menu's items, a sidebar's places, a row of tabs) that moves to the one under the pointer and to the one with keyboard focus, and takes its size. It is `aria-hidden`, under the items' text, never takes a press (`pointer-events: none`), and goes when the pointer leaves and nothing inside has focus. The items' own hover and focus styles stay; the box only adds a travelling fill.

It is drawn only when a brand sets `--cd-draw-highlight` (`glide`, `ease`, `bouncy`, `slow` or `pop`, from the Pro motion-drawings item), so an app without that choice sees exactly what it saw before. In forced colours it is hidden, and the items' own hover and focus styles carry the state.

Render it inside the container `containerRef` points at; the container becomes its containing block (AppSidebar, DropdownMenu, Tabs and NavRail do this for you). Items are found by `itemSelector` from where the pointer or focus is.

- Registry name: `glide-highlight` (`@candor/glide-highlight`)
- 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/glide-highlight

## Install

```bash
npx candor-ui add glide-highlight
```

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

## Example

```tsx
"use client"

import * as React from "react"
import { GlideHighlight } from "@/components/candor/ui/glide-highlight"

const ways = ["glide", "ease", "bouncy", "slow", "pop"] as const

export default function Example() {
  const list = React.useRef<HTMLDivElement>(null)
  const [way, setWay] = React.useState<(typeof ways)[number]>("glide")
  return (
    // A brand turns the box on with --cd-draw-highlight (here on the wrapper; usually on :root in the brand file).
    <div className="flex flex-col gap-3" style={{ "--cd-draw-highlight": way } as React.CSSProperties}>
      <label className="flex items-center gap-2 text-xs text-muted-foreground">
        --cd-draw-highlight
        <select value={way} onChange={(e) => setWay(e.target.value as (typeof ways)[number])} className="rounded-md border bg-background px-2 py-1 text-xs text-foreground">
          {ways.map((w) => (
            <option key={w}>{w}</option>
          ))}
        </select>
      </label>
      <div ref={list} className="w-56 rounded-lg border p-1 text-sm">
        <GlideHighlight containerRef={list} itemSelector="button" />
        <ul>
        {["Overview", "Activity", "Members", "Settings"].map((label) => (
          <li key={label}>
            <button type="button" className="w-full rounded-md px-2 py-1.5 text-start outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80">
              {label}
            </button>
          </li>
        ))}
        </ul>
      </div>
    </div>
  )
}
```

## <GlideHighlight>

Props:

- `containerRef: React.RefObject<HTMLElement | null>`: The element that holds the items. It gets `position: relative` and `isolation: isolate` if it has neither, so the box sits under the items.
- `itemSelector: string`: A CSS selector that matches one item ("[data-slot=dropdown-menu-item]"). The item is the closest match to where the pointer or focus is.
- `className?: string | undefined`: Classes for the box, merged with Candor's.

## Motion

- **glide** (move): The box behind a list's items travels to the item under the pointer and takes its size. It is drawn only when the brand turns it on, starts in place, goes straight to an item reached with the keyboard, and jumps under reduced motion.

Set with `--cd-motion-glide-highlight-<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`.
