Concepts
In-app guides
Six things Loop can render inside your product: a checklist, a tooltip, a modal, a banner, an inline card, and a multi-step tour. You do not build any of them. You designate where they may appear, and Loop's SDK does the rest, inside a shadow root, so your CSS and ours can never reach each other.
The six types
| Type | Needs from your DOM | Content it carries |
|---|---|---|
| checklist | Nothing | title, 1–12 steps, sequential, progress strings |
| tooltip | anchor: a CSS selector | title, body, placement, optional CTA |
| modal | Nothing | title, up to 10 paragraphs, CTA and secondary |
| banner | slot, if mode is push | text up to 280 characters, edge, mode |
| inline | slot: a CSS selector, always | title, body, appearance, cta_kind |
| tour | A selector per step, or null for a centred step | version, 2–12 steps, resume_window_days |
What we touch in your page
- One open shadow root per mount. Your CSS cannot match into it and ours cannot leak out. It is open, not closed, so your own tooling can still see us.
- Anchors are selectors into a page we did not ship. The SDK treats a resolved anchor as a fact with a short shelf life. If your app has not painted the element yet, the guide waits, mounted nowhere and seen by nobody, and the wait is cancelled the moment the route changes.
- A banner never moves your layout uninvited. The default, overlay, costs zero layout shift and accepts that it may cover edge content, so it is thin and always dismissible. Push shifts the page once, at mount, and is legal only into a slot you designated.
- hideSurfaces() takes all of it back off: mounts, listeners, observers, timers.
Loop ships no copy
Every user-facing string, including the aria labels, travels on the wire as authored content. There is no default English Dismiss hiding in the bundle, because a default English Dismiss injected into a French product is our bug, not your oversight.
Interpolated strings are templates and the SDK substitutes rather than concatenates. Translations carry copy and never structure: a translation cannot change a step's completion rule, a tooltip's anchor, or which steps exist, because a French variant with different rules is not a translation, it is a second product that will be measured as if it were the first.
Targeting, triggers and caps
| Field | What it does |
|---|---|
| segment_id | Who is eligible. Null means everyone. |
| trigger.url_contains | Where it may show. Empty means any page. A substring, not an equality test: URLs carry query strings and ids nobody can predict. |
| frequency | max_views over window_days. One view per seven days is once a week. An absent max_views is uncapped. Enforced on the server, per request. |
| priority | 0–100. Which one wins when two are eligible at once. |
| theme | auto or custom. brand_color is a hex string; an unreadable or absent one falls back to a neutral theme that still passes AA. Loop never invents a brand it cannot read. |
| experiment | An experiment_id and a variant_key, or null. Both halves or neither: an experiment_id with no arm is a surface that cannot say which side of the test it is on. The variant_key can never be holdout: the holdout is the people who see nothing. |