Coachmark
A coachmark is an anchored popover that spotlights and explains a single feature, typically used in onboarding, feature discovery, or guided tours.
Use it sparingly and at most one at a time, paired with a visual anchor on the feature being highlighted. A coachmark teaches; it should not block the user from continuing their task.
Example
Show demo markup
<div class="coachmark" aria-label="an anchored popover that spotlights and explains a single feature">Coachmark</div>
Usage example
<script lang="ts">
import Coachmark from './Coachmark.svelte';
let open = $state(true);
</script>
<Coachmark
{open}
title="New dashboard"
description="See your activity at a glance."
dismissLabel="Dismiss"
onDismiss={() => (open = false)}
/>
Show Svelte source
// In your Svelte component:
import Coachmark from "lily-design-system-svelte-headless/components/Coachmark/Coachmark.svelte";
<Coachmark>
<!-- content -->
</Coachmark>
Usage
<Coachmark
open={open}
title="Try the new filter"
description="Filter by status to find items faster."
dismissLabel="Dismiss"
onDismiss={handleDismiss}
/>
Props
open: boolean (default false) -- whether the coachmark is visible
title: string (required) -- heading text
description: string (optional) -- body text
dismissLabel: string (required) -- accessible label for the dismiss button
onDismiss / ondismiss: callback (optional) -- dismiss click handler
...restProps: any additional HTML attributes
Keyboard Interactions
- Tab / Shift+Tab: Move focus into and out of the coachmark
- Enter / Space: Activate the dismiss button when focused
- Escape: (Consumer-provided) call
onDismiss to close the coachmark
ARIA
role="dialog" on the container
aria-modal="false" (non-blocking; does not trap focus or hide background)
aria-labelledby references the title id
aria-describedby references the description id (only when description is provided)
hidden attribute reflects !open
When to Use
- Use for onboarding a single new feature
- Use for guided tours that highlight one element at a time
- Use when the dismissal is non-blocking and the user can continue working
When Not to Use
- Do not use for blocking confirmations — use
Dialog or AlertDialog
- Do not stack multiple coachmarks at once — sequence them
- Do not use for help that should be persistently available — use
ContextualHelp or Tooltip
Headless
Renders a <div role="dialog" aria-modal="false"> with title/description ids and an always-rendered dismiss button. Anchor positioning, fade/scale animations, and visual treatment are entirely the consumer's responsibility.
Styles
Consumer CSS targets the coachmark class. Sub-elements expose coachmark-title, coachmark-description, and coachmark-dismiss classes for selective styling. Provide a clear focus indicator on the dismiss button.
Testing
- Verify the component renders a
<div> with role="dialog" and class coachmark
- Verify
aria-modal="false"
- Verify the title is rendered inside
<h2 class="coachmark-title"> and linked via aria-labelledby
- Verify the description, when provided, is rendered and linked via
aria-describedby
- Verify the description and
aria-describedby are absent when no description is provided
- Verify
hidden is set when open is false
- Verify the dismiss button has
aria-label={dismissLabel} and fires onDismiss on click
Advice
- Designers: Anchor the coachmark visually to the feature; do not obscure other actions.
- Developers: Persist the dismissed state in user preferences so the coachmark does not reappear.
Implementation Notes
- Uses a
<div> with role="dialog" and aria-modal="false" (non-blocking)
title is rendered in an <h2 class="coachmark-title"> and linked via aria-labelledby
description, when provided, is rendered in a <p class="coachmark-description"> and linked via aria-describedby
- The dismiss button always renders and uses
dismissLabel as aria-label
- The whole coachmark is hidden via the
hidden attribute when open is false
- IDs for the title and description are generated stably via
useId, crypto.randomUUID, or a Math.random helper
Related components
popover — a floating content box anchored to a trigger element
tooltip — a small popup showing descriptive text on hover or focus
tour — a tour guide, such as for sightseeing, or pathways, or demonstrations, etc.
References
- WAI-ARIA Dialog: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
- Adobe Spectrum Coachmark: https://spectrum.adobe.com/page/coach-mark/
- WCAG 1.4.13 Content on Hover or Focus: https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: coachmark
- PascalCase: Coachmark
- Description: an anchored popover that spotlights and explains a single feature
- Status: beta — implemented and unit-tested in all seven frameworks with an axe-clean demo page; not yet exercised in composed flows
- HTML tag: <div> with role="dialog"
- CSS class: .coachmark
- Interactive: yes (dismiss button)
ARIA
role="dialog" on the container aria-modal="false" (non-blocking) aria-labelledby referencing the title id aria-describedby referencing the description id, only when description is provided hidden attribute reflects !open - Dismiss button has
aria-label via dismissLabel
Keyboard
- Tab / Shift+Tab: Move focus into and out of the coachmark
- Enter / Space: Activate the dismiss button when focused
- Escape: (Consumer-provided) dismiss the coachmark
Props
open: boolean (default false) title: string (REQUIRED) description: string (optional) dismissLabel: string (REQUIRED) onDismiss / ondismiss: callback (optional) ...restProps: any additional HTML attributes