Track highlight
Shared fluid hover, keyboard-focus, and selection highlight for custom lists and groups, with CSS anchors and a measured fallback.
Examples
Sliding selection
Use a relative, isolated container with data-track="slide", mark its controls with data-track-item, and place TrackHighlight after them. data-active="true" identifies the selection. Hover or keyboard focus previews another item; leaving returns to the selection. Keep hit areas touching for continuous hover: the highlight follows actual targets, without nearest-item detection or gap clicks.
Composition
Custom hover or selection track
Set data-track="hover" or "slide" on the container; mark the selected item with data-active.
- <div>
- items with data-track-item
- <Button />
- <TrackHighlight />
- items with data-track-item
Installation
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/extensions/track-highlight.tsx.
npx shadcn@latest add https://control-ui.dev/r/track-highlight.jsonDependencies
src/registry/sources/control-ui/extensions/create-track-highlight.tsSupportsrc/registry/sources/control-ui/extensions/supports-anchor-transitions.tsSupportRaw code
Primary installed source
"use client";import type { ComponentProps, CSSProperties, ReactNode, RefObject } from "react";import { useEffect, useRef } from "react";import { supportsAnchorTransitions } from "@/components/control-ui/extensions/supports-anchor-transitions";import type { TrackHighlightKnobStyle } from "@/components/control-ui/knob-contracts/track-highlight-knobs";import { cn } from "@/components/control-ui/lib/cn";const trackHighlightStructureClasses = "pointer-events-none absolute -z-10";const defaultItemSelector = "[data-track-item]";const defaultActiveSelector = '[data-track-item][data-active="true"]';const loadTrackHighlight = () => import("@/components/control-ui/extensions/create-track-highlight");export type TrackHighlightProps = Omit<ComponentProps<"div">, "children" | "ref" | "style"> & { trackRef?: RefObject<HTMLElement | null>; itemSelector?: string; activeSelector?: string; range?: boolean; followHover?: boolean; hoverClassName?: string; children?: ReactNode; style?: CSSProperties & TrackHighlightKnobStyle;};export function TrackHighlight({ trackRef, itemSelector = defaultItemSelector, activeSelector = defaultActiveSelector, range, followHover, hoverClassName, className, style, children, ...props}: TrackHighlightProps) { const ref = useRef<HTMLDivElement>(null); const hoverRef = useRef<HTMLDivElement>(null); const layered = Boolean(hoverClassName); const usesAnchorAnatomy = !trackRef && !range && !layered && followHover !== false && itemSelector === defaultItemSelector && activeSelector === defaultActiveSelector; useEffect(() => { const highlight = ref.current; const track = trackRef?.current ?? highlight?.parentElement; if (!track || !highlight) return; const usesAnchors = usesAnchorAnatomy && track.hasAttribute("data-track") && supportsAnchorTransitions(); if (usesAnchors) return; const hoverHighlight = layered ? (hoverRef.current ?? undefined) : undefined; let cancelled = false; let dispose: (() => void) | undefined; loadTrackHighlight().then(({ createTrackHighlight }) => { if (cancelled) return; dispose = createTrackHighlight(track, highlight, { itemSelector, activeSelector, range, followHover }, hoverHighlight); }); return () => { cancelled = true; dispose?.(); }; }, [trackRef, itemSelector, activeSelector, range, followHover, layered, usesAnchorAnatomy]); return ( <> <div {...props} ref={ref} data-control-ui="track-highlight" data-control-family="track-highlight" data-slot="root" data-positioning={usesAnchorAnatomy ? "anchor" : undefined} aria-hidden style={style} className={cn(trackHighlightStructureClasses, className)} > {children} </div> {layered ? ( <div ref={hoverRef} data-control-ui="track-highlight" data-control-family="track-highlight" data-slot="hover" aria-hidden style={style} className={cn(trackHighlightStructureClasses, hoverClassName)} /> ) : null} </> );}Knobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-track-highlight-* · 6 knobsHow the cascade resolves--cui-track-highlight-radius<length-percentage>var(--radius-popup-item)--cui-track-highlight-background<color>var(--card)--cui-track-highlight-hover-background<color>color-mix(in oklab, var(--card) 92%, var(--foreground) 8%)--cui-track-highlight-ring-color<color>oklch(from var(--foreground) l c h / 0.05)--cui-track-highlight-shadow*var(--shadow-sm)--cui-track-highlight-transition-duration<time>var(--duration-fast)