Skip to content

cv-guidance-anchor

Lightweight semantic anchor wrapper for product guidance registration.

cv-guidance-anchor is a registration primitive only. It does not render guidance UI, own guidance progress, compute eligibility, or import app-layer guidance models. The wrapper host itself is the registered target element.

Usage

View source
html
<div class="guidance-anchor-demo-shell" data-demo="guidance-anchor" data-live-demo-height="640">
  <section class="guidance-anchor-demo-brief" aria-labelledby="guidance-anchor-demo-title">
    <div class="guidance-anchor-demo-copy">
      <span class="guidance-anchor-demo-kicker">Anchor registration</span>
      <h3 id="guidance-anchor-demo-title">Connect guidance to the real product action.</h3>
      <p>
        The wrapper stays visually transparent. It publishes stable metadata so an app guidance host can
        resolve where a coach mark, inline hint, or blocked-action panel belongs.
      </p>
    </div>

    <dl class="guidance-anchor-demo-flow" aria-label="Guidance anchor contract">
      <div>
        <dt>Action</dt>
        <dd>Upload file</dd>
      </div>
      <div>
        <dt>Anchor id</dt>
        <dd>files.create-or-upload</dd>
      </div>
      <div>
        <dt>Host receives</dt>
        <dd>register event</dd>
      </div>
    </dl>
  </section>

  <section
    class="guidance-anchor-demo-workspace"
    data-guidance-demo-workspace
    aria-label="Guided file toolbar preview"
  ></section>

  <template data-guidance-demo-template>
    <div class="guidance-anchor-demo-product">
      <header class="guidance-anchor-demo-toolbar">
        <div class="guidance-anchor-demo-title">
          <span>Files</span>
          <strong>Secure vault</strong>
        </div>

        <div class="guidance-anchor-demo-actions" aria-label="File actions">
          <cv-button>New folder</cv-button>
          <cv-guidance-anchor anchor-id="files.create-or-upload" surface="files" owner="files">
            <span class="guidance-anchor-demo-target">
              <cv-button variant="primary" data-guidance-demo-action>Upload file</cv-button>
            </span>
          </cv-guidance-anchor>
        </div>
      </header>

      <div class="guidance-anchor-demo-content">
        <div class="guidance-anchor-demo-list" aria-label="File list preview">
          <div>
            <span class="guidance-anchor-demo-file">Recovery-codes.txt</span>
            <span class="guidance-anchor-demo-meta">encrypted</span>
          </div>
          <div>
            <span class="guidance-anchor-demo-file">Border-notes.md</span>
            <span class="guidance-anchor-demo-meta">local vault</span>
          </div>
          <div>
            <span class="guidance-anchor-demo-file">Client-archive.zip</span>
            <span class="guidance-anchor-demo-meta">sealed</span>
          </div>
        </div>

        <div class="guidance-anchor-demo-callout">
          <span class="guidance-anchor-demo-rail" aria-hidden="true"></span>
          <cv-guidance-panel variant="coach-mark" density="compact">
            <span slot="icon" aria-hidden="true">i</span>
            <span slot="title">Upload encrypted file</span>
            <p>Anchor ready. This panel is rendered by a host after it receives the anchor registration.</p>
            <button slot="actions" type="button" data-guidance-action="primary">Got it</button>
            <button slot="actions" type="button" data-guidance-action="secondary">Later</button>
          </cv-guidance-panel>
        </div>
      </div>
    </div>
  </template>

  <section class="guidance-anchor-demo-inspector" aria-labelledby="guidance-anchor-demo-event-title">
    <div>
      <span class="guidance-anchor-demo-kicker">Observed event</span>
      <h4 id="guidance-anchor-demo-event-title">What the app guidance host receives</h4>
    </div>
    <output data-guidance-demo-log aria-live="polite">Waiting for anchor registration...</output>
    <cv-button size="small" data-guidance-demo-remount>Remount anchor</cv-button>
  </section>
</div>

<script>
  document
    .querySelectorAll('.guidance-anchor-demo-shell[data-demo="guidance-anchor"]:not([data-ready])')
    .forEach((shell) => {
      shell.dataset.ready = 'true'

      const workspace = shell.querySelector('[data-guidance-demo-workspace]')
      const template = shell.querySelector('template[data-guidance-demo-template]')
      const log = shell.querySelector('[data-guidance-demo-log]')
      const remount = shell.querySelector('[data-guidance-demo-remount]')

      const setLog = (message) => {
        if (log) log.textContent = message
      }

      shell.addEventListener('guidance-anchor-register', (event) => {
        const detail = event.detail ?? {}
        shell.dataset.anchorState = 'registered'
        setLog(`guidance-anchor-register -> ${detail.surface}:${detail.anchorId} (owner: ${detail.owner})`)
      })

      shell.addEventListener('guidance-anchor-unregister', (event) => {
        const detail = event.detail ?? {}
        shell.dataset.anchorState = 'unregistered'
        setLog(`guidance-anchor-unregister -> ${detail.surface}:${detail.anchorId}`)
      })

      const mountAnchor = () => {
        if (!workspace || !template) return
        workspace.replaceChildren(template.content.cloneNode(true))
      }

      remount?.addEventListener('click', () => {
        workspace?.replaceChildren()
        window.setTimeout(mountAnchor, 160)
      })

      mountAnchor()
    })
</script>

Minimal registration markup:

View source
html
<cv-guidance-anchor anchor-id="files.create-or-upload" surface="files" owner="files">
  <button type="button">Upload</button>
</cv-guidance-anchor>

Anatomy

<cv-guidance-anchor> (host)
└── <slot>

Attributes

AttributeTypeDefaultDescription
anchor-idString""Stable semantic identifier for the UI action or area.
surfaceString""Product surface where the anchor is available.
ownerString""Owning feature or team metadata.

Slots

SlotDescription
(default)Action or content visually associated with anchor

CSS Parts

None.

CSS Custom Properties

None. cv-guidance-anchor is a light wrapper with display: contents by default.

Visual States

Host selectorDescription
:hostUses display: contents for wrapper layout.
:host([hidden])Hides the wrapper and its slotted content.

Events

EventWhenDetail
guidance-anchor-registerOn connect and semantic update{anchorId, surface, owner, element}
guidance-anchor-unregisterOn disconnect{anchorId, surface, owner, element}

Both events bubble and are composed so an app-level guidance host can observe anchors across shadow boundaries. detail.element is always the cv-guidance-anchor host.

Behavior

  • Re-dispatches guidance-anchor-register when anchor-id, surface, or owner changes.
  • Does not query slotted children, child shadow roots, global document selectors, translated text, or coordinates.
  • Complex components whose real target is inside Shadow DOM should dispatch the same events manually with the actual HTMLElement.

ChromVoid UIKit documentation