{"_id":"@apollovisionlabs/guide-unstyled","name":"@apollovisionlabs/guide-unstyled","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@apollovisionlabs/guide-unstyled","version":"0.1.0","type":"module","license":"MIT","sideEffects":["*.css"],"main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./styles.css":"./styles.css"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/apollovisionlabs/guide.git"},"keywords":["react","onboarding","product-tour","walkthrough","unstyled","headless","accessibility"],"dependencies":{"@apollovisionlabs/guide-core":"0.3.1"},"peerDependencies":{"react":"^19","react-dom":"^19"},"scripts":{"build":"tsup","test":"vitest run","typecheck":"tsc --noEmit"},"_id":"@apollovisionlabs/guide-unstyled@0.1.0","description":"A rendering layer for [`@apollovisionlabs/guide-core`](../core/README.md) that draws the tour, the checklist and the hotspots as plain DOM elements: no UI toolkit, no CSS-in-JS, no design tokens. Every part carries a `guide-` class and a `data-guide-part`","bugs":{"url":"https://github.com/apollovisionlabs/guide/issues"},"homepage":"https://github.com/apollovisionlabs/guide#readme","_integrity":"sha512-MyRBfi7WGup/0S6BQULE8pTT7BiibD/txR7BCekVVSmze/KXSbdyWtyfWAFDu/To1thWxAdLsxCOQULYKo7zEA==","_resolved":"/tmp/apollovisionlabs-guide-unstyled-0.1.0.tgz","_from":"file:/tmp/apollovisionlabs-guide-unstyled-0.1.0.tgz","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-MyRBfi7WGup/0S6BQULE8pTT7BiibD/txR7BCekVVSmze/KXSbdyWtyfWAFDu/To1thWxAdLsxCOQULYKo7zEA==","shasum":"f462b5126c8841bf0ac31d90d73dd4c9c5b4ddd1","tarball":"https://registry.npmjs.org/@apollovisionlabs/guide-unstyled/-/guide-unstyled-0.1.0.tgz","fileCount":10,"unpackedSize":257838,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIE32mOWR5DsBFibMBB1AwoybrNNSDEELC7ibFw9cWzreAiEA1M5zsKe/Lg3nku0em6lzuTXf+pRUAaVLzzy6zMA8qCA="}]},"_npmUser":{"name":"remydeme","email":"contact@apollovisionlabs.com"},"directories":{},"maintainers":[{"name":"remydeme","email":"contact@apollovisionlabs.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/guide-unstyled_0.1.0_1788635862675_0.1615685960134008"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T19:17:42.563Z","0.1.0":"2026-09-05T19:17:42.905Z","modified":"2026-09-05T19:17:43.123Z"},"maintainers":[{"name":"remydeme","email":"contact@apollovisionlabs.com"}],"description":"A rendering layer for [`@apollovisionlabs/guide-core`](../core/README.md) that draws the tour, the checklist and the hotspots as plain DOM elements: no UI toolkit, no CSS-in-JS, no design tokens. Every part carries a `guide-` class and a `data-guide-part`","homepage":"https://github.com/apollovisionlabs/guide#readme","keywords":["react","onboarding","product-tour","walkthrough","unstyled","headless","accessibility"],"repository":{"type":"git","url":"git+https://github.com/apollovisionlabs/guide.git"},"bugs":{"url":"https://github.com/apollovisionlabs/guide/issues"},"license":"MIT","readme":"# @apollovisionlabs/guide-unstyled\n\nA rendering layer for [`@apollovisionlabs/guide-core`](../core/README.md) that draws the tour, the\nchecklist and the hotspots as plain DOM elements: no UI toolkit, no CSS-in-JS, no design tokens.\nEvery part carries a `guide-` class and a `data-guide-part` attribute and nothing else, so you can\nstyle it with your own CSS, adopt the stylesheet this package ships, or use both (the stylesheet\nsets appearance only; anything more specific you write still wins).\n\n## Installation\n\n```bash\npnpm add @apollovisionlabs/guide-core @apollovisionlabs/guide-unstyled\n```\n\nPeer dependencies are `react` and `react-dom`, both `^19`. Beyond those and\n`@apollovisionlabs/guide-core`, this package has no other dependency: no UI toolkit, no styling\nruntime.\n\n## Two ways to use it\n\n**Without the stylesheet.** Import the components and render them under `GuideProvider` (and\n`ChecklistProvider` / `HotspotProvider`, as needed) exactly as documented in the\n[root README](../../README.md). Almost everything is unstyled HTML carrying its `guide-` class,\nwith no spacing, border or typography of its own, ready for your own stylesheet to target. Five\nthings do ship a visible default, because without them the layer is not plain, it is broken:\n\n| What | Default | How to change it |\n| --- | --- | --- |\n| The three floating surfaces (popover, hotspot bubble, launcher panel) | `background: var(--guide-surface, #ffffff)` and `color: var(--guide-ink, #111111)`, set inline | Set `--guide-surface` / `--guide-ink` on any ancestor. |\n| The spotlight overlay | a 50% black fill, an SVG presentation attribute | Any CSS `fill` rule, or `--guide-overlay` with the stylesheet loaded. |\n| The launcher progress ring | `stroke=\"currentColor\"`, an SVG presentation attribute | Any CSS `stroke` or `color` rule. |\n| The hotspot marker's dot | `fill=\"currentColor\"`, an SVG presentation attribute | Any CSS `fill` or `color` rule. |\n| The checklist progress bar | a 4px track in `--guide-border` with a fill in `--guide-primary`, set inline | Set `--guide-bar-height`, `--guide-border` or `--guide-primary`. |\n\nThe surfaces' two colours are inline because a transparent panel prints its text straight over\nthe page copy behind it, with no boundary at all, which is unreadable rather than plain; the\nprogress bar's are inline because a bar with no height and no colour draws nothing at all, and a\ncomponent that says it renders a bar and renders nothing is absent rather than plain. All of them\nare `var()` references rather than flat colours on purpose: a flat inline colour would beat every rule\nan adopter writes and make the package unthemeable, whereas this renders correctly with no\nstylesheet at all and still yields to one custom property set anywhere above it. The three SVG\ndefaults are presentation attributes, which lose to any CSS declaration, for the same reason.\n\n**With the stylesheet.** Import the CSS once, anywhere in your app:\n\n```ts\nimport '@apollovisionlabs/guide-unstyled/styles.css'\n```\n\nThis gives every part a default look: colour, spacing, borders, radius, typography, the strike\nthrough on a completed checklist item and the pulse on a hotspot marker. It never sets\n`position`, `top`, `left`, `z-index` or `pointer-events` on a part the components already\nposition, so it never fights the placement or stacking the components compute themselves. Retheme\nit by setting the custom properties listed below; anything you declare on `.guide-*` yourself\nstill overrides it with ordinary CSS specificity, including the spotlight's `fill` and the\nlauncher ring's `stroke`, which the components set as SVG presentation attributes for exactly this\nreason.\n\n## Components\n\n### `GuideTour`\n\nRenders the active tour step: `Spotlight` plus `StepPopover`. Reads the current step from\n`useGuideStep()` (from `@apollovisionlabs/guide-core`); render one `<GuideTour />` under\n`GuideProvider` and nothing else is required to show a running tour.\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `zIndex` | `number` | `1300` | Passed to both `Spotlight` and `StepPopover`, which sits one above it. |\n| `padding` | `number` | `8` | Passed to `Spotlight`. |\n| `radius` | `number` | `8` | Passed to `Spotlight`. |\n| `labels` | `Partial<StepPopoverLabels>` | see `StepPopover` | Passed to `StepPopover`. |\n\nWhile a step's target is awaited (not yet resolved), `GuideTour` renders nothing visible but still\nlistens for `Escape` to stop the tour, because neither `Spotlight` nor `StepPopover` is mounted\nduring the wait.\n\n### `Spotlight`\n\nThe dimmed overlay with a hole cut over the target. Exported for advanced composition; `GuideTour`\nrenders it for you in the normal case.\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `rect` | `Rect \\| null` | none | The target's rectangle. Renders nothing when `null`. |\n| `padding` | `number` | `8` | Margin, in pixels, between the target and the edge of the hole. |\n| `radius` | `number` | `8` | Corner radius, in pixels, of the hole. |\n| `interactive` | `boolean` | `false` | When `true`, the overlay is `pointer-events: none` and a click never stops the tour. |\n| `zIndex` | `number` | `1300` | Stacking level of the overlay. |\n| `onDismiss` | `() => void` | none | Called on a click outside the hole, when not `interactive`. |\n\n### `StepPopover`\n\nThe tour's dialog: title, body, step count and navigation buttons.\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `anchorEl` | `HTMLElement \\| null` | none | The element the popover positions against. |\n| `open` | `boolean` | none | Whether to render. |\n| `title` | `string` | none | Step title. |\n| `body` | `string` | none | Step body. |\n| `stepIndex` / `stepCount` | `number` | none | Shown as `stepIndex + 1 / stepCount`. |\n| `isFirst` / `isLast` | `boolean` | none | Hides the back button on the first step; shows the `finish` label on the last. |\n| `placement` | `Placement` | `'bottom'` | Preferred side; flips and clamps to the viewport (see \"Positioning\" below). |\n| `zIndex` | `number` | `1300` | Stacking level. |\n| `describeElement` | `HTMLElement \\| null` | none | Element that receives `aria-describedby`, pointing at the body, for as long as the popover is open. |\n| `modal` | `boolean` | `true` | Traps focus when `true`. Pass `false` for an interactive step, so the user can reach the page. |\n| `awaitsAction` | `boolean` | `false` | Replaces the primary button with the `awaitingAction` label and ignores `ArrowRight`. |\n| `labels` | `Partial<StepPopoverLabels>` | see below | Button and status wording. |\n| `onNext` / `onPrevious` / `onStop` | `() => void` | none | Called by the matching button or keyboard shortcut. |\n\n`StepPopoverLabels` defaults: `{ next: 'Next', previous: 'Back', finish: 'Finish', close: 'Close', awaitingAction: 'Click the highlighted element to continue.' }`.\n\n`Escape` stops the tour, `ArrowRight` advances, `ArrowLeft` goes back; all three are ignored while\nfocus is in a text input, and `ArrowRight` is also ignored while `awaitsAction` is set.\n\n**Rendering it standalone: pass `modal={false}`.** With `modal` left at its default the popover\nemits `aria-modal=\"true\"`, which tells a screen reader that everything outside it is inert. Under\n`GuideTour` that is true, because the `Spotlight` overlay is mounted with it and really does swallow\nthe page. Rendered on its own it is not: this layer applies neither `aria-hidden` nor `inert` to\nanything, so the claim would be a promise nothing keeps, exactly the one `ChecklistLauncher`'s panel\ndeliberately does not make. Pass `modal={false}` unless you are supplying an inert layer of your\nown, in which case keep the default and the claim is yours to honour.\n\n### `Checklist`\n\nRenders a checklist inline: a progress bar, one row per item with a checkbox and a dismiss button.\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `checklistId` | `string` | none | Passed to `useChecklist`. |\n| `title` | `string` | none | Rendered as a heading when set. |\n| `onDismiss` | `() => void` | none | Called after the checklist is dismissed. |\n| `onActivate` | `(item: ResolvedChecklistItem) => void` | none | Called after any row is activated. |\n| `labels` | `Partial<ChecklistLabels>` | see below | Wording. |\n\n`ChecklistLabels` defaults: `{ dismiss: 'Dismiss', progress: (completed, total) => \\`${completed} of ${total}\\`, markComplete: (title) => \\`Mark ${title} as complete\\`, markNotComplete: (title) => \\`Mark ${title} as not complete\\` }`.\n\nRenders nothing until the checklist's own restore from storage has settled\n(`useChecklist(checklistId).restored`), so a checklist already dismissed or partly completed never\nflashes its pre-restore state for one paint.\n\nThe progress bar is determinate: `guide-checklist-bar` is the track and `guide-checklist-bar-fill`\ninside it carries the percentage as an inline `width`, so the same number a screen reader reads off\n`aria-valuenow` is the one a sighted user sees. The track's height and both colours are inline too,\nthrough `--guide-bar-height`, `--guide-border` and `--guide-primary`, so the bar is drawn with no\nstylesheet loaded and still rethemes from those three variables. The stylesheet adds only its\nmargin and its corner radius.\n\n**Differs from `@apollovisionlabs/guide-mui`:** this `Checklist` announces its progress through\n`useAnnouncer` whenever it changes, so a screen reader user ticking items hears \"2 of 4\" the way a\nsighted user reads it off the bar. The MUI layer's `Checklist` does not. This is deliberate, not an\noversight on either side, but it does mean the two layers are not identical to assistive\ntechnology; the MUI layer is expected to gain the announcement rather than this one to lose it.\n\n### `ChecklistLauncher`\n\nWraps `Checklist` behind a floating button showing `completedCount/total`, opened as a panel.\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `checklistId` | `string` | none | Passed to `Checklist` and `useChecklist`. |\n| `title` | `string` | none | Passed to `Checklist`; also the panel's accessible name. |\n| `placement` | `'bottom-right' \\| 'bottom-left' \\| 'top-right' \\| 'top-left'` | `'bottom-right'` | Corner of the viewport the button sits in. |\n| `zIndex` | `number` | `1299` | Stacking level of the button; the panel sits one above it. |\n| `labels` | `Partial<ChecklistLauncherLabels>` | see below | Wording, extends `ChecklistLabels`. |\n\n`ChecklistLauncherLabels` adds `fabLabel: (title, completed, total) => \\`${title}, ${completed} of ${total} complete\\`` (the button's accessible name; the ring around it is decorative and invisible to a screen reader) and `dismissed: (title) => \\`${title} dismissed\\`` (a focus destination announced when the panel is dismissed).\n\nThe panel closes on `Escape` and on a click anywhere outside it (the launcher button itself\nexcepted, which toggles it), mirroring the hotspot bubble in this same package. Focus returns to\nthe launcher button either way.\n\nThe panel is a `role=\"dialog\"` that traps focus, but it deliberately carries **no `aria-modal`**.\nThis layer applies neither `aria-hidden` nor `inert` to the rest of the application, so a screen\nreader's virtual cursor can still reach the page behind the panel, and claiming `aria-modal=\"true\"`\nwould promise an inertness that is not there. The MUI layer's panel is rendered by MUI's own\n`Modal`, which does apply `aria-hidden` to the rest of the app, so it makes the claim honestly.\n\nDismissing from inside the panel removes both the button and the panel in the same commit. Because\nnothing would be left to return focus to, an off-screen status message takes focus for a moment\nand then removes itself, so a keyboard user hears the dismissal confirmed instead of landing on\n`document.body`.\n\n### `Hotspots`\n\nRenders a marker at each unseen hotspot's target; clicking it opens a bubble with the hotspot's\ntitle, body, and, when it names a `tourId`, a button that starts that tour.\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `labels` | `Partial<HotspotLabels>` | see below | Wording. |\n| `placement` | `Placement` | `'bottom'` | Where the bubble opens relative to the marker. Overridable per hotspot through `Hotspot.placement`. |\n| `zIndex` | `number` | `1299` | Stacking level of the marker; the bubble sits one above it. |\n\n`HotspotLabels` defaults: `{ marker: (title) => \\`Show what is new: ${title}\\`, startTour: 'Show me', close: 'Close' }`.\n\nRenders nothing while a tour is running or paused (read through `GuideContext`, tolerating its\nabsence), nothing until the hotspot's own restore from storage has settled, and no marker for a\ntarget with no rendered size (a `display: none` target, for instance).\n\n## Parts\n\nEvery part below carries the listed class and `data-guide-part`. These are a stable contract:\nbuild selectors against them.\n\n| Element | class | `data-guide-part` |\n| --- | --- | --- |\n| spotlight svg | `guide-spotlight` | `spotlight` |\n| popover container | `guide-popover` | `popover` |\n| popover header | `guide-popover-header` | `popover-header` |\n| popover title | `guide-popover-title` | `popover-title` |\n| popover body | `guide-popover-body` | `popover-body` |\n| popover footer | `guide-popover-footer` | `popover-footer` |\n| step counter | `guide-popover-count` | `popover-count` |\n| waiting sentence | `guide-popover-awaiting` | `popover-awaiting` |\n| next or finish button | `guide-button guide-button-primary` | `popover-next` |\n| back button | `guide-button` | `popover-previous` |\n| close button | `guide-button guide-button-icon` | `popover-close` |\n| checklist container | `guide-checklist` | `checklist` |\n| checklist heading | `guide-checklist-title` | `checklist-title` |\n| checklist progress text | `guide-checklist-progress` | `checklist-progress` |\n| checklist progress bar | `guide-checklist-bar` | `checklist-bar` |\n| checklist progress fill | `guide-checklist-bar-fill` | `checklist-bar-fill` |\n| checklist item row | `guide-checklist-item` | `checklist-item` |\n| checklist item checkbox | `guide-checklist-check` | `checklist-check` |\n| checklist dismiss button | `guide-button` | `checklist-dismiss` |\n| launcher button | `guide-launcher` | `launcher` |\n| launcher ring | `guide-launcher-ring` | `launcher-ring` |\n| launcher panel | `guide-launcher-panel` | `launcher-panel` |\n| hotspot marker | `guide-hotspot` | `hotspot` |\n| hotspot bubble | `guide-hotspot-bubble` | `hotspot-bubble` |\n| hotspot bubble title | `guide-hotspot-title` | `hotspot-title` |\n| hotspot bubble body | `guide-hotspot-body` | `hotspot-body` |\n| hotspot bubble actions | `guide-hotspot-actions` | `hotspot-actions` |\n| hotspot tour button | `guide-button guide-button-primary` | `hotspot-tour` |\n| hotspot close button | `guide-button` | `hotspot-close` |\n\nThe popover and the hotspot bubble also carry `data-guide-placement`, the side `usePosition`\nactually resolved (which can differ from the requested `placement` after a flip). A completed\nchecklist item's row carries `data-guide-complete=\"true\"` (or `\"false\"`), which is how the\nstylesheet's strike through is applied without the component choosing a text decoration itself.\n\nA few structural elements carry a class but no `data-guide-part`, because nothing outside their\nown parent ever needs to address them directly: `guide-checklist-header`, `guide-checklist-list`,\n`guide-checklist-item-button`, `guide-checklist-item-title`, `guide-checklist-item-body` and\n`guide-launcher-anchor`. They are still valid, stable selectors for a stylesheet.\n\nOne class, `guide-visually-hidden`, is applied to the focus-destination node the launcher renders\non dismissal, but that node is hidden by an inline style set directly on it regardless of any\nstylesheet (the same precedent `announcerNode` in `@apollovisionlabs/guide-core` follows), so the\nshipped stylesheet has no rule for it and one would have no visible effect.\n\n## Custom properties\n\nThe shipped stylesheet reads every colour through one of these, each with a fallback. Three of\nof them are also read inline by the components themselves, so they work with no stylesheet loaded:\n`--guide-surface` and `--guide-ink` on the three floating surfaces, `--guide-launcher-offset` on the\nlauncher's corner inset, and `--guide-bar-height`, `--guide-border` and `--guide-primary` on the\nchecklist progress bar.\n\n| Property | Fallback | Used for |\n| --- | --- | --- |\n| `--guide-surface` | `#ffffff` | Background of the popover, the launcher panel, the checklist and the hotspot bubble. |\n| `--guide-ink` | `#111111` | Primary text colour. |\n| `--guide-muted` | `#6b6b6b` | Secondary text: progress counts, the step counter, the waiting sentence, item bodies. |\n| `--guide-border` | `#d9d9d9` | Borders and the checklist progress track. |\n| `--guide-primary` | `#2563eb` | Primary buttons, the launcher button and ring, the hotspot marker and its pulse, the checklist checkbox. |\n| `--guide-primary-ink` | `#ffffff` | Text on a primary button and on the launcher button. |\n| `--guide-overlay` | `rgba(0, 0, 0, 0.5)` | The spotlight's dimmed background. |\n| `--guide-launcher-offset` | `24px` | Distance between the checklist launcher and the two viewport edges of its corner. |\n| `--guide-bar-height` | `4px` | Thickness of the checklist progress bar. |\n| `--guide-radius` | `8px` | Corner radius shared by the popover, panel, bubble, checklist, buttons and progress track. |\n\n`--guide-launcher-offset` is the one to reach for when the launcher has to clear a fixed cookie\nbanner or a mobile tab bar: it moves the button away from both edges of whichever corner\n`placement` puts it in.\n\nSet any subset on a wrapping element (or `:root`) to retheme:\n\n```css\n:root {\n  --guide-primary: #7c3aed;\n  --guide-surface: #1c1c1e;\n  --guide-ink: #f5f5f5;\n}\n```\n\n## Positioning\n\n`StepPopover`, `ChecklistLauncher`'s panel and `Hotspots`' bubbles all position through the same\n`usePosition` / `computePosition` pair: pick a side, flip to the opposite side if the preferred one\ndoesn't fit, then clamp both axes to the viewport unconditionally, because a bubble that hangs off\nthe edge of the window is unusable, not merely imperfect.\n\n`ChecklistLauncher`'s panel centres on its button on the cross axis and clamps to the viewport,\nrather than aligning to whichever corner the button sits in the way the MUI layer's `Popper` does.\nA launcher pinned to `bottom-right`, for instance, opens a panel centred above the button, not\nflush with the button's right edge.\n\n## Limits\n\n- **Positioning is against the viewport, not a scrolling ancestor.** A target inside its own\n  scroll container, near that container's edge rather than the window's, is not corrected for.\n  Every floating part renders through a `Portal` into `document.body` specifically to avoid one\n  class of positioning bug (an ancestor with `transform`, `filter` or `contain` breaking `fixed`\n  positioning), but scroll containers are not accounted for.\n- **Positioning clamps against the layout viewport.** `document.documentElement.clientWidth` and\n  `clientHeight`, which exclude a space-taking scrollbar, rather than `window.innerWidth` and\n  `innerHeight`, which include it: every rect they are compared against comes from\n  `getBoundingClientRect`, which excludes it too.\n- **No runtime dependency beyond `@apollovisionlabs/guide-core`.** No UI toolkit, no styling\n  runtime. Peer dependencies are `react` and `react-dom` (`^19` each).\n- **The default stacking of the hotspots and the checklist launcher share a layer.** Both default\n  to the same `zIndex` (1299, one below the tour's 1300) when neither is given an explicit\n  `zIndex` prop, so an ambient hotspot marker and the checklist launcher button can end up at the\n  same stacking level, ordered only by DOM order. Give one of them an explicit `zIndex` if you\n  need a guaranteed order between them.\n\n## Compatibility\n\n| | Supported |\n| --- | --- |\n| React | 19 |\n| Rendering | ESM and CommonJS, with `\"use client\"` for Next's App Router |\n","readmeFilename":"README.md","_rev":"1-7f61157208722b1660d7c7ad59c6a337"}