Anchors

Marking elements, and why not CSS selectors.

Declaring anchors

Every element a tour can point at lives in one registry. Flows reference the registry, never a raw string, so renaming a key breaks the flow file at compile time.

walkthrough/anchors.ts
import { defineAnchors } from "@cairnkit/core";

export const anchors = defineAnchors({
  questions: {
    tabCreate: "questions.tab-create",
    save: "questions.save",
  },
});

Applying them

One spread. Your components import nothing else from Cairn, and stay unaware they are part of a tour.

import { anchor } from "@cairnkit/core";

<button {...anchor(anchors.questions.save)}>Save</button>

For config-driven UI such as a sidebar, put the id on the item and let the renderer forward it as data-cairn.

Components that swallow props

Some third-party components do not forward unknown props. <TourAnchor> wraps them without adding a box to the layout.

import { TourAnchor } from "@cairnkit/react";

<TourAnchor id={anchors.questions.save}>
  <ThirdPartyButton />
</TourAnchor>

How resolution works

  • Matches are filtered to visible elements first.
  • A missing anchor is waited for, via MutationObserver, up to waitForMs.
  • If a resolved element later disappears, resolution restarts.

Why not CSS selectors

A selector like .btn-primary:nth-child(2) is a guess about structure that nothing enforces. Rename the class, reorder the DOM, and the tour breaks with no signal. A declared anchor is a contract: the compiler checks the reference, and CI checks the element still exists.