# Glide Highlight

> One highlight for a whole list, which glides from row to row on a spring instead of each row lighting on its own.

- Name: `glide-highlight`
- Status: beta
- Categories: motion, display
- File: the install writes `glide-highlight.tsx` to your `ui` alias, `@/components/ui` by default.
- Gallery: https://arbokui.com/components/glide-highlight
- Registry item: https://arbokui.com/r/glide-highlight.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/glide-highlight
   ```

## Use it for

- A list, menu, or row of links where the pointer sweeps across many rows. One highlight that glides reads as one motion, where a highlight per row blinks.
- Call useGlide with the list's ref and the highlight's ref. Call follow(row, event) from pointerover and pointermove, to(row) from focus or highlight events, and hide() when they end.
- follow leans the highlight toward the pointer during a quick sweep, so it crosses rows at an even speed. It settles back onto its row once the pointer rests.
- A list that already marks one row, like data-highlighted, needs no hook. Pass that attribute as track, and the highlight follows the marked row by itself.
- Give a row data-tone, like danger, and the highlight takes that tone when it arrives there.
- Pass instant for moves the keys make, so a held arrow key always shows one row fully lit.
- Style each row's text with data-\[lit\]:. A row gets data-lit once the highlight is more than halfway onto it, so text and highlight arrive together.

## Avoid it for

- A single control's hover. One button needs no glide. Its own hover fill is enough.
- Showing the selected row. The highlight follows the pointer and the keys. Mark the chosen row with its own fill or aria-current.
- A list that is not positioned and isolated. The highlight sits behind the rows, so the list needs both, or it measures wrong and hides behind the page.

## Props

- `useGlide`: `(listRef: RefObject<HTMLElement | null>, highlightRef: RefObject<HTMLSpanElement | null>, options?: { axis?: "x" | "y"; span?: "row" | "full" }) => Glide`. Helper. Moves the highlight. axis is "y" for a column and "x" for a row of links. span "row" fits each row, and "full" spans the list's width so it only moves up and down.
- `Glide`: `{ follow: (row: HTMLElement, pointer: { clientX: number; clientY: number }) => void; to: (row: HTMLElement, move?: { lead?: number; instant?: boolean }) => void; hide: (options?: { instant?: boolean }) => void; press: (pressed: boolean) => void; row: () => HTMLElement | null }`. Type. follow sends it to the row under the pointer and leans it ahead on a quick sweep. to sends it to a row: it fades in there the first time and glides after that. hide fades it out where it is. press darkens it. row is the row it is on.
- `GlideHighlight`: `ComponentProps<"span">`. Part. The highlight. Put it first inside the list, with the same ref, and give it its fill as a class, like bg-foreground/8.
- `span`: `"row" | "full"`. Default `"row"`. On GlideHighlight. Match the span you passed useGlide.
- `track`: `string`. On GlideHighlight. An attribute that marks one row in its parent at a time, like data-highlighted. The highlight then follows that row with no hook: it glides when the pointer moved the mark and jumps when the keys did. It sets data-glide on the parent, so rows can drop their own fill there.

## Accessibility

- The highlight is hidden from screen readers. It only shows where the pointer or the keys are.
- It does not take focus or show focus. Keep each row's own focus ring, or draw the ring on the highlight while it sits on the focused row.
- Reduced motion, or motion off, makes it jump from row to row instead of gliding.
- Forced colors paint its fill away. Give the lit row an outline there, so the highlight still shows.

## Examples

### Glide one highlight down a list as the pointer moves

```tsx
import { useRef } from "react";

function GlideList() {
  const list = useRef<HTMLUListElement>(null);
  const highlight = useRef<HTMLSpanElement>(null);
  const glide = useGlide(list, highlight);
  return (
    <ul
      ref={list}
      className="relative isolate flex flex-col"
      onPointerMove={(event) => {
        const row = (event.target as Element).closest("button");
        if (row) glide.follow(row, event);
      }}
      onPointerLeave={() => glide.hide()}
    >
      <GlideHighlight ref={highlight} className="rounded-md bg-foreground/8" />
      {["Inbox", "Drafts", "Sent"].map((label) => (
        <li key={label}>
          <button type="button" className="w-full px-3 py-2 text-left text-muted-foreground data-[lit]:text-foreground">
            {label}
          </button>
        </li>
      ))}
    </ul>
  );
}

<GlideList />
```

### Follow the row a list already marks, with no hook

```tsx
<ul role="listbox" className="relative isolate">
  <GlideHighlight track="data-highlighted" className="rounded-md bg-accent" />
  {/* Rows that set data-highlighted on the active one */}
</ul>
```

### Jump with no glide when the arrow keys move the highlight

```tsx
glide.to(row, { instant: true });
```
