Mark required questions
Add a Required tag to questions the customer must answer before they can submit. The cart won't accept the order until each one has a value.
Choose 1
Overview
A coach mark is a 280 px popover anchored to a target element. The surface is the inverse accent — pure black — so it reads as a deliberate interruption of the ordinary page chrome. One title, one short body, an optional preview, and an optional two-button action row. Nothing else.
Coach marks teach. Tooltips remind. Banners announce.
If the user has seen the control before, use a tooltip on hover. If the message is for everyone on this surface (incident, outage, policy change), use a banner. The coach mark is for moments that need to land once — a new feature, the next step of an onboarding flow, a setting whose effect lives on a different screen.
Anatomy
Five parts. Title, body, optional preview, optional action row, and the arrow that anchors the whole thing to a target.
Required questions
Add a Required tag to questions the customer must answer.
Choose 1
Arrow
12 × 12 diamond on the popover's top edge — same fill as the surface,
background.accent.inverse.subtlest. Three positions:right,left,center.Title
Inter Tight Semibold 16 / 24 in
text.inverse. Sentence case. One line — wraps to two only when the language requires it.Body
Inter Tight Regular 14 / 20 in
text.inverseat 92% opacity (lighter type on dark surfaces wants more air). Cap at three lines so the popover never grows past ~220 px tall without a preview.Preview (optional)
248 × 200 image area on
background.accent.neutral.subtlerwith a 1 pxborder.accent.inversehairline. Only when the result of the action lives on a different screen.Actions (optional)
Two buttons, equal-width. Secondary (transparent + white border + white text) on the left, primary (white surface + black text) on the right. Used for multi-step tours; not for single-shot coach marks.
Arrow positions
The popover always sits below its target. Three horizontal arrow positions place the tip directly above the element being described, regardless of where the popover itself lands. Pick the position that puts the arrow under the visual centre of the target.
right
Position aware
The arrow shifts horizontally so it always lands on the target.
Arrow sits 16 px in from the right edge. Use when the target sits at the right side of a row — column headers, end-of-row controls.
left
Position aware
The arrow shifts horizontally so it always lands on the target.
Arrow sits 16 px in from the left edge. Use when the target sits at the left — list rows, primary navigation items.
center
Position aware
The arrow shifts horizontally so it always lands on the target.
Arrow centers under the popover. Use when the target sits roughly in the middle — page-level toolbars, centred CTA bars.
Content variants
Title + body is the floor. Preview and actions earn their place one at a time, never reflexively.
Title and body
Drag to reorder
Hold the handle and drag a row to change its place in the list. Order is saved automatically.
The minimum. One sentence to point to the target, one sentence to explain. No preview when the target is already visible behind the popover.
+ preview image
Required questions
Add a Required tag to a question to block the order until the customer answers.
Choose 1
Use a preview when the result of the action sits on a different screen — onboarding, feature announcements, settings pages with downstream effects the user can't see.
+ actions
A new way to refund
Refunds now group by transaction so you can issue a partial refund without splitting the order.
Two buttons — secondary on the left (outline on inverse), primary on the right (white on inverse). Use the action row only when the coach mark is part of a multi-step tour.
Full
Required questions
Add a Required tag to a question to block the order until the customer answers.
Choose 1
Title + body + preview + actions. Use sparingly — full coach marks dominate the screen and lose impact when stacked.
Multi-step tour
A guided tour stitches a sequence of coach marks together — each one anchored to the next target. The action row carries Back / Next; the secondary button on step one becomes Skip. A small dash progress indicator sits below the row so the user can see how many steps are left.
Required questions
Add a Required tag to questions the customer must answer.
Step 1 of 3
Live demo — click Next / Back to step through.
Behavior
- Click outside dismisses. Anywhere outside the popover closes it. The interaction is non-modal — the user can keep working with the page underneath. This is the default reflex for coach marks; if you need to block the user, you wanted a Modal.
- Once-per-user, by default. Persist a flag against the user account when the coach mark is dismissed or its primary action is taken. Don't show it again on the next visit; that's how an introduction becomes a nag.
- One coach mark on screen at a time. Multiple coach marks at once is a UI emergency — the user has no idea where to look. In a tour, the previous mark must animate out before the next one animates in.
- Anchor with a 16 px gap. The arrow tip sits 16 px below the bottom edge of the target. Closer than that the arrow looks pasted on; further away the user has to track to find what the popover is pointing at.
- Reposition before flipping. When the popover would clip the viewport, shift it horizontally first (and update the arrow position to match). Only flip to above the target if there's no horizontal room either.
- Reduced motion respected. The default entrance is a 120 ms 4 px rise + opacity. Under
prefers-reduced-motion: reducethe popover appears in place with opacity only.
Accessibility
- Roles: the popover is
role="dialog"witharia-modal="false"(the user is not blocked) andaria-labelledbypointing at the title element. - Focus management:open the popover and move focus to the primary action (or the close button if there are no actions). Trap focus inside the popover until it's dismissed; restore focus to the target on close.
- Keyboard:
Escapedismisses;Tab/Shift+Tabcycles through the actions; in a tour,→advances and←goes back when no action button has focus. - Contrast: the dark surface gives
text.inverseonbackground.accent.inverse.subtlestan effective contrast of 21 : 1 — well past the AA requirement. The body text runs at 92% opacity (~16.5 : 1) so it still clears AA at small sizes. - Announcement: on open, an off-screen
aria-live="polite"region announces “Tip: {title}” so screen-reader users know a coach mark has appeared without losing their place. - Touch targets: the action buttons are 48 px tall — the platform tap-target floor. The arrow is decorative and not focusable.
Code
The component composes from a single CoachMark primitive and a positioning hook (useCoachMark) that handles anchoring, dismissal, and the once-per-user flag.
tsx
import { CoachMark, useCoachMark } from "@flatpay-dk/ui";
// Single-shot — anchors to the ref, persists dismissal under "required-questions"
const { ref, open, dismiss } = useCoachMark({
id: "required-questions",
showOnce: true,
});
return (
<>
<button ref={ref}>Required</button>
{open ? (
<CoachMark
position="right"
title="Required questions"
body="Add a Required tag to a question to block the order until the customer answers."
primaryLabel="Got it"
secondaryLabel="Skip"
onPrimary={dismiss}
onSecondary={dismiss}
/>
) : null}
</>
);
// Multi-step tour
const tour = useCoachTour({
id: "burger-config",
steps: [
{ target: pattyRef, title: "Required questions", body: "..." },
{ target: tempRef, title: "Defaults", body: "..." },
{ target: extraRef, title: "Optional toppings", body: "..." },
],
});
return (
<CoachMark
position={tour.position}
title={tour.step.title}
body={tour.step.body}
secondaryLabel={tour.isFirst ? "Skip" : "Back"}
primaryLabel={tour.isLast ? "Done" : "Next"}
onSecondary={tour.back}
onPrimary={tour.next}
/>
);Best practices
Coach marks are easy to abuse. The questions below catch the most common misuses before they reach the screen.
Required questions
Add a Required tag — the customer can't submit without an answer.
Choose 1
Do
Use a coach mark to introduce a control whose effect lives somewhere else. The preview shows what the user can't see from here.
This is a Save button
Click it to save your changes.
Don't
Don't use a coach mark for a label the user can read. Tooltip on hover is the right reach.
One mark, one moment
Lands, lands once, then leaves the user alone.
Do
Pin one coach mark per screen. The popover lands; the user reads it; the popover dismisses; back to work.
First mark
Pointing at one target.
Second mark
Pointing at a different target. Now what?
Don't
Don't stack two coach marks. The user has no idea which target is being described — and the second arrow lies.
Once is enough
The user accepts the message; the system remembers; it doesn't reopen.
Do
Persist dismissal. Once a coach mark is closed or completed, it stays closed for that user.
Welcome back!
Here's that thing you already know about, again.
Don't
Don't reopen the same coach mark every visit. Repetition turns help into harassment.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | — | Inter Tight Semibold 16/24, text.inverse. Sentence case. One line preferred. |
| body | ReactNode | — | Inter Tight Regular 14/20, text.inverse at 92% opacity. Cap at three lines without a preview. |
| preview | ReactNode | — | Optional 248 × 200 image slot — typically a screenshot mock that shows the result of the action. |
| primaryLabel | string | — | Right action button. Pair with secondaryLabel — both must be set for the action row to render. |
| secondaryLabel | string | — | Left action button. Becomes 'Skip' on the first step of a tour, 'Back' on subsequent steps. |
| onPrimary | () => void | — | Click handler for the primary action. Default behaviour dismisses the coach mark. |
| onSecondary | () => void | — | Click handler for the secondary action. Default behaviour dismisses without persisting completion. |
| position | "right" | "left" | "center" | "right" | Horizontal arrow position relative to the popover. Pick the one that lands the arrow under the target. |