KQ-1693 · WAYFINDING/DOTS
Wayfinding Dots
Non-blocking hint dots that sit on the corners of the features nobody has opened yet, each breathing slowly from one shared clock with its own phase. Pressing a dot flicks it up on springs.flick, frames the feature on springs.snap and unfolds the tip out of the dot as a clip circle growing from its centre; closing folds it back and the dot flies on springs.glide along an arc into the corner counter, whose digit rolls on springs.snap and pill lands on springs.recoil. Dots are buttons in reading order: Enter opens, Escape closes and moves focus to the next dot, H shows or hides them all, and under 420px the tip is a bottom sheet you can swipe away.
Scheduled3
Replace valve
Dock 4
09:30DueMRHedge trim
Pier 9 offices
11:00TF
In progress3
Boiler service
Basin Yard
08:00DueKAFence repair
Fernworks depot
10:30PN
0 of 5 found · nothing found yet
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/wayfinding-dots
Props
- children
- ReactNode
- default —
- The interface the hints sit on. Mark each feature with data-wayfinding="<target>".
- hints
- WayfindingHint[]
- default defaultWayfindingHints
- The hints, { id, target, title, body, placement?, keys? }, in the order they are numbered.
- found / defaultFound
- string[]
- default — / []
- Ids of the hints already found, controlled or initial.
- onFoundChange
- (ids: string[]) => void
- default —
- Fires from the close, the use or the Dismiss all that found a hint, with every found id.
- onOpen
- (hint: WayfindingHint) => void
- default —
- A dot was opened.
- onDismiss
- (hint: WayfindingHint, via: WayfindingDismissVia) => void
- default —
- A hint was closed and counted as found: via its button, Escape, a press outside, a swipe of the sheet or using the feature.
- onDismissAll
- (ids: string[]) => void
- default —
- Dismiss all was pressed, with the ids it found.
- shown / defaultShown
- boolean
- default — / true
- Whether the dots are showing, controlled or initial.
- onShownChange
- (shown: boolean) => void
- default —
- Fires from the hotkey or the eye button that showed or hid the dots.
- hotkey
- string | null
- default "h"
- The key that shows and hides every dot while focus is inside the component and not in a text field; null turns it off.
- dotStyle
- "beacon" | "halo" | "ring" | "pip"
- default "beacon"
- The mark: a ring that leaves the dot, a swelling glow, a breathing hollow ring, or a numbered badge.
- breathSpeed
- number
- default 1
- How fast the dots breathe, 0.25 to 2; 1 is one breath every 3.2 s.
- accent
- string
- default "var(--accent-bright)"
- The dots, the highlight frame and the counter ring. Any CSS colour; dots take its hue at a fixed lightness so they read in both themes.
- discoverOnUse
- boolean
- default true
- Using a feature yourself counts as finding it: its dot leaves for the counter without a tip.
- counter
- "top-start" | "top-end" | "bottom-start" | "bottom-end"
- default "bottom-end"
- The corner the discovered counter sits in.
- sheetBelow
- number
- default 420
- Below this container width, in px, tips dock as a bottom sheet.
- label
- string
- default "Feature hints"
- The hint layer's accessible name; the found count is added to it.
- disabled
- boolean
- default false
- The dots stay but cannot be opened, the hotkey is off and the counter's buttons are inert.
- className
- string
- default —
- Classes for the wrapper around the interface.
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | The interface the hints sit on. Mark each feature with data-wayfinding="<target>". |
| hints | WayfindingHint[] | defaultWayfindingHints | The hints, { id, target, title, body, placement?, keys? }, in the order they are numbered. |
| found / defaultFound | string[] | — / [] | Ids of the hints already found, controlled or initial. |
| onFoundChange | (ids: string[]) => void | — | Fires from the close, the use or the Dismiss all that found a hint, with every found id. |
| onOpen | (hint: WayfindingHint) => void | — | A dot was opened. |
| onDismiss | (hint: WayfindingHint, via: WayfindingDismissVia) => void | — | A hint was closed and counted as found: via its button, Escape, a press outside, a swipe of the sheet or using the feature. |
| onDismissAll | (ids: string[]) => void | — | Dismiss all was pressed, with the ids it found. |
| shown / defaultShown | boolean | — / true | Whether the dots are showing, controlled or initial. |
| onShownChange | (shown: boolean) => void | — | Fires from the hotkey or the eye button that showed or hid the dots. |
| hotkey | string | null | "h" | The key that shows and hides every dot while focus is inside the component and not in a text field; null turns it off. |
| dotStyle | "beacon" | "halo" | "ring" | "pip" | "beacon" | The mark: a ring that leaves the dot, a swelling glow, a breathing hollow ring, or a numbered badge. |
| breathSpeed | number | 1 | How fast the dots breathe, 0.25 to 2; 1 is one breath every 3.2 s. |
| accent | string | "var(--accent-bright)" | The dots, the highlight frame and the counter ring. Any CSS colour; dots take its hue at a fixed lightness so they read in both themes. |
| discoverOnUse | boolean | true | Using a feature yourself counts as finding it: its dot leaves for the counter without a tip. |
| counter | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "bottom-end" | The corner the discovered counter sits in. |
| sheetBelow | number | 420 | Below this container width, in px, tips dock as a bottom sheet. |
| label | string | "Feature hints" | The hint layer's accessible name; the found count is added to it. |
| disabled | boolean | false | The dots stay but cannot be opened, the hotkey is off and the counter's buttons are inert. |
| className | string | — | Classes for the wrapper around the interface. |