# Coach mark

> An onboarding spotlight: the screen dims around a cut-out over the measured target, and Next morphs the hole to the next target, changing shape and size in flight, while the tooltip glides alongside and a ring pulses on arrival.

- Type: Component (overlays)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/coach-mark
- Markdown: https://goodui.dev/native/components/coach-mark.md
- Registry item: https://goodui.dev/native/r/coach-mark.json
- Dependencies: react-native-reanimated
- Keywords: react native coach mark, onboarding tour, spotlight tutorial, product tour, feature walkthrough, tooltip tour, expo walkthrough

## When to use

- A short first-run tour of three to five controls on a busy screen: filters, the compose button, a tab.
- Pointing at one new feature after an update (a single step).

## When not to use

- Explaining the product before sign-up: use page-dots-onboarding.
- A hint that appears on demand next to one control: use popover.
- Anything the person must read to use the screen: put it in the screen itself.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/coach-mark.json
npx expo install --fix
```

The component lands in `components/goodui-native/coach-mark/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs button, locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { useRef, useState, type ComponentRef } from "react";
import { View } from "react-native";
import { CoachMark } from "@/components/goodui-native/coach-mark/coach-mark";
import { IconButton } from "@/components/goodui-native/icon-button/icon-button";
import { SearchBar } from "@/components/goodui-native/search-bar/search-bar";
import { ComposeIcon } from "./icons"; // your own icon

export function InboxScreen() {
  const searchRef = useRef<ComponentRef<typeof View>>(null);
  const composeRef = useRef<ComponentRef<typeof View>>(null);
  const [tour, setTour] = useState(true);
  return (
    <View style={{ flex: 1, padding: 16, gap: 16 }}>
      <View ref={searchRef} collapsable={false}>
        <SearchBar placeholder="Search messages" />
      </View>
      {/* message list */}
      <View ref={composeRef} collapsable={false} style={{ position: "absolute", right: 16, bottom: 32 }}>
        <IconButton icon={(c, s) => <ComposeIcon color={c} size={s} />} accessibilityLabel="New message" variant="solid" tone="accent" />
      </View>
      <CoachMark
        open={tour}
        onOpenChange={setTour}
        steps={[
          { target: searchRef, title: "Find any message", description: "Search names, words and attachments." },
          { target: composeRef, shape: "circle", title: "Start a conversation", description: "Write to one person or a group." },
        ]}
      />
    </View>
  );
}
```

## Examples

### Attach a target

```tsx
<View ref={filtersRef} collapsable={false}>
  <ChipGroup options={filters} value={value} onValueChange={setValue} />
</View>
```

### Tap anywhere to continue, localized

```tsx
<CoachMark
  open={open}
  onOpenChange={setOpen}
  steps={steps}
  backdropPress="next"
  labels={{ next: "Weiter", back: "Zurück", skip: "Überspringen", done: "Fertig", stepOf: (s, t) => `${s} von ${t}` }}
/>
```

## API reference

### CoachMark

`import { CoachMark } from "@/components/goodui-native/coach-mark/coach-mark";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `steps` (required) | `CoachMarkStep[]` | – | The tour, in order. |
| `open` (required) | `boolean` | – | Whether the tour is showing. |
| `onOpenChange` (required) | `(open: boolean) => void` | – | Called with `false` when the tour closes (finish, skip, Escape, Android back). |
| `step` | `number` | – | Controlled step index. Pair with `onStepChange`. |
| `defaultStep` | `number` | `0` | Uncontrolled first step. Default 0. |
| `onStepChange` | `(step: number) => void` | – | Called with the new step index on Next and Back. |
| `onFinish` | `() => void` | – | Called when Done is pressed on the last step. |
| `onSkip` | `(step: number) => void` | – | Called with the step index when the tour is skipped or dismissed early. |
| `labels` | `Partial<CoachMarkLabels>` | – | Overrides for the built-in strings (buttons, step counter); a GooduiLocaleProvider pack sets them app-wide. |
| `backdropPress` | `"next" \| "close" \| "none"` | `"none"` | What a tap on the dimmed area does. Default `none`. |
| `haptic` | `boolean` | `true` | Button and step haptics. Default `true`. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- The tooltip is modal (accessibilityViewIsModal; role dialog, aria-modal and aria-labelledby the step title on the web).
- Each step announces 'N of M', its title and description.
- Skip, Back and Next/Done are goodui Buttons. Escape on the web and the Android back button skip the tour.
- The dim blocks touches to the app; with backdropPress set it is a button labelled after what it does (Next, Done or Skip tour).

## Motion

- Open: the dim fades in and the hole starts 44 pt wider than the target and closes in on it (a focus pull) on the morph spring.
- Next and Back: the hole's position, size and corner radius spring to the next target on the morph spring, so a wide card hole becomes a round button hole in flight; the tooltip glides on the smooth spring, its arrow tracks the target and flips sides with a fade; the step text crossfades.
- A 2 pt accent ring pulses out of the hole once per arrival.
- Reduced motion: the hole and tooltip move at once and the content crossfades; no focus pull, no ring.

## Haptics

- select on every step change, tap on Skip, success on Done. haptic={false} turns them off.

## Performance

- The dim is one view with a very wide border whose inner edge is the hole, so the cut-out is plain layout animated on the UI thread: no svg masks, no snapshots.
- Targets are measured once per step and when the window size changes.

## Notes for AI

- Each step's target is a ref to a View with collapsable={false} that is on screen; scroll it into view before opening or before that step.
- shape='circle' for icon buttons, FABs and avatars; padding sets the gap around the element.
- Control open; the component calls onOpenChange(false) on Done, Skip, Escape and back. onFinish runs on Done, onSkip(step) on an early exit.
- Keep tours to five steps or fewer and copy to one sentence each.

## Related

- [Popover](https://goodui.dev/native/components/popover.md): A bubble anchored to an element whose arrow always points at the anchor's center, even when the bubble is pushed in from the screen edge; it flips above or below to fit and unfurls out of the arrow tip.
- [Onboarding pager](https://goodui.dev/native/components/page-dots-onboarding.md): A full-screen onboarding pager: pages follow the finger 1:1 with parallax on their content, the page dot stretches toward the next page like a worm, and the Next button grows with the swipe into a full-width Get started on the last page.
- [Button](https://goodui.dev/native/components/button.md): A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.

## Guidance for AI tools

Coach mark: An onboarding spotlight: the screen dims around a cut-out over the measured target, and Next morphs the hole to the next target, changing shape and size in flight, while the tooltip glides alongside and a ring pulses on arrival. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
