> ## Documentation Index
> Fetch the complete documentation index at: https://docs.funeli.top/llms.txt
> Use this file to discover all available pages before exploring further.

# Components: actions & loaders

> Reference for the Funeli actions & loaders components: what each one is for, every setting with its type and default, and a working example.

Move the visitor forward; checkout buttons live here too. Add these components to a step in the editor and change their settings in the inspector, or ask an agent to; the settings below are the same in both.

## Button

Type `Button`. Advances, checks out, or links out. action next validates every input on the screen (errors show inline) then goes on; checkout opens payment for the selected plan (needs a PlanSelector on the same step); url opens a link; scroll jumps within the screen without validating. Busy/disabled with a spinner while next or checkout is in flight. sticky (default) pins it to the bottom bar. Label \<=24 chars. disabledUntil gates it on answers; appearance box tokens style the button itself.

| Setting | Type | Required | Description |
| - | - | - | - |
| `label` | string | yes | Button text, keep \<=24 visible chars (validate\_funnel warns above, except for the link variant); start with a verb, e.g. "Continue", "Start my plan" |
| `subtitle` | string | no | Small second line under the label, keep \<=24 chars, e.g. "Save 50%" |
| `action` | "next" \| "checkout" \| "url" \| "scroll" | no | next: validate every input on the screen (errors appear inline) then go on. checkout: open payment for the selected plan (needs a PlanSelector on the same step). url: open url. scroll: smoothly scroll to scrollTarget without validating or advancing. Busy (spinner, disabled) while next or checkout is in flight Default: `"next"`. |
| `url` | string | no | Absolute URL opened by action "url"; required for it, ignored otherwise |
| `scrollTarget` | string | no | For action "scroll": component id or DOM id to scroll to on the same screen; a leading # is optional. Required for it, ignored otherwise |
| `variant` | "primary" \| "secondary" \| "link" | no | primary = solid brand CTA (one per screen). secondary = outlined, for a second choice. link = underlined text, for low-priority actions like "Skip" Default: `"primary"`. |
| `size` | "sm" \| "md" \| "lg" | no | Button size: sm 40px, md 56px (used when omitted), lg 64px for a hero CTA |
| `icon` | "activity" \| "alarm" \| "apple" \| "award" \| "baby" \| "battery" \| "bed" \| "bike" \| "brain" \| "calendar" \| "check" \| "chart" \| "clock" \| "coffee" \| "dumbbell" \| "flame" \| "flower" \| "footprints" \| "gift" \| "heart" \| "leaf" \| "lightning" \| "lock" \| "moon" \| "mountain" \| "music" \| "person" \| "ruler" \| "salad" \| "scale" \| "shield" \| "smile" \| "sparkles" \| "star" \| "sun" \| "target" \| "thumbs-up" \| "timer" \| "trophy" \| "upload" \| "users" \| "water" \| "yoga" \| "x" \| "zap" \| "heart-pulse" \| "stethoscope" \| "pill" \| "syringe" \| "thermometer" \| "weight" \| "hand-heart" \| "eye" \| "ear" \| "bone" \| "carrot" \| "cherry" \| "cookie" \| "pizza" \| "sandwich" \| "egg" \| "fish" \| "beef" \| "wheat" \| "cup-soda" \| "wine" \| "candy" \| "soup" \| "milk" \| "waves" \| "medal" \| "flag" \| "goal" \| "bed-double" \| "cloud-moon" \| "sunrise" \| "sunset" \| "wind" \| "feather" \| "brain-circuit" \| "book-open" \| "headphones" \| "glasses" \| "home" \| "briefcase" \| "car" \| "plane" \| "map-pin" \| "globe" \| "dog" \| "cat" \| "shopping-bag" \| "wallet" \| "credit-card" \| "piggy-bank" \| "gamepad" \| "tv" \| "smartphone" \| "laptop" \| "camera" \| "palette" \| "graduation-cap" \| "smile-plus" \| "frown" \| "meh" \| "laugh" \| "angry" \| "heart-crack" \| "party-popper" \| "check-circle" \| "x-circle" \| "alert-triangle" \| "info" \| "help-circle" \| "plus" \| "minus" \| "arrow-right" \| "trending-up" \| "trending-down" \| "bar-chart" \| "pie-chart" \| "percent" \| "tag" \| "badge-check" \| "crown" \| "gem" \| "rocket" \| "key" \| "bell" \| "mail" \| "phone" \| "message-circle" \| "search" \| "settings" \| "sliders" \| "refresh" \| "infinity" \| "hourglass" \| "calendar-check" \| "list-checks" \| "clipboard" \| "file-text" \| "lightbulb" \| "puzzle" \| "magnet" \| "anchor" \| "user-check" \| "user-plus" \| "users-round" \| "handshake" \| "thumbs-down" \| "star-half" | no | Icon beside the label; leave empty for plain text buttons |
| `iconPosition` | "left" \| "right" | no | Side of the label the icon sits on; right suits arrows Default: `"left"`. |
| `sticky` | boolean | no | Pin to the bottom bar (safe-area aware, solid background); several sticky buttons stack. false = sits in the page flow, use for secondary or inline buttons Default: `true`. |
| `pulse` | boolean | no | Subtle 1.6 s scale pulse to draw the eye to the primary CTA; paused while busy and off under reduced motion. Use on at most one button per screen Default: `false`. |
| `badge` | object | no | Small pill overlapping the top edge of the button, e.g. "-50%" |
| `disabledUntil` | object\[] | no | Keep the button disabled until all conditions hold on funnel answers/computed fields, e.g. \[\{"field":"photo\_url","op":"exists"}] or \[\{"field":"email","op":"exists"}]. It stays focusable and is announced as unavailable |
| `disabledHint` | string | no | Short reason shown under a gated button while it is disabled, e.g. "Upload a photo to continue"; read by screen readers |
| `iconSize` | integer | no | Icon size, px (default 18) |
| `iconColor` | string | no | Icon colour; default the label colour |
| `subtitlePosition` | "below" \| "above" | no | Second line under (default) or over the label |
| `subtitleSize` | "xs" \| "sm" | no | Second line size, default xs |
| `subtitleColor` | string | no | Second line colour; default the label colour |

> [accordion] Example
>
> ```json theme={null}
> {
>   "label": "Continue",
>   "subtitle": "Takes less than a minute",
>   "action": "next",
>   "variant": "primary",
>   "size": "lg",
>   "icon": "arrow-right",
>   "iconPosition": "right",
>   "sticky": true,
>   "pulse": true,
>   "badge": {
>     "text": "RECOMMENDED",
>     "tone": "primary",
>     "style": "soft"
>   }
> }
> ```

## Loader

Type `Loader`. Shows timed progress, then advances. Runs once per mount over durationMs; the checklist ticks off with the percentage and results appear per line. autoAdvance goes on at 100% (after any step hook answers), so the step needs no Button. An optional mid-way question pauses progress until answered. Exposed as a progressbar. Checklist rows may reveal a right-side value (text, pill or badge) in theme tones; legacy result/resultBadge still work. question.display "sheet" asks in a modal bottom sheet that must be answered.

| Setting | Type | Required | Description |
| - | - | - | - |
| `title` | string | no | Headline under the progress ring (or above the bar), keep \<=40 chars Default: `"Creating your personal plan"`. |
| `steps` | string \| object\[] | no | Checklist ticked off evenly as progress passes each item, 0-6 lines. A plain string is label-only; an object may add value \{text, variant, tone, icon} on the right. Legacy result/resultBadge are still accepted Default: `[]`. |
| `durationMs` | integer | no | Milliseconds from 0 to 100%, 1000-300000: 3000-6000 for a quick "analysing" beat, up to 70000 for a long "searching" screen (use steps or a question to keep it alive) Default: `5000`. |
| `style` | "ring" \| "bar" \| "dots" | no | ring: circular progress with the percentage inside. bar: headline then horizontal progress bar. dots: calm pulsing indicator for open-ended work (percentage, if shown, is small text) Default: `"ring"`. |
| `showPercent` | boolean | no | Show the number, e.g. "42%"; set false for dots or when the wait is not measurable Default: `true`. |
| `autoAdvance` | boolean | no | Go on when it reaches 100% (after the step hook, if any, has answered); false = stays at 100% and needs a Button. Not allowed inside checkoutSheet Default: `true`. |
| `footer` | string | no | Small line under the checklist, keep \<=60 chars, e.g. "Profile located. Finalizing the full report..."; supports links |
| `footerLive` | boolean | no | Adds a pulsing red dot before the footer text Default: `false`. |
| `question` | object | no | Pauses the loader once, mid-progress, to ask a quick single-choice question and keep attention; resumes the same total duration after an answer. The visitor cannot skip it. display chooses an inline card or a modal bottom sheet |

> [accordion] Example
>
> ```json theme={null}
> {
>   "title": "Creating your personal plan",
>   "steps": [
>     {
>       "text": "Analyzing your answers",
>       "value": {
>         "text": "Complete",
>         "variant": "text",
>         "tone": "success",
>         "icon": "check"
>       }
>     },
>     {
>       "text": "Matching your goals",
>       "value": {
>         "text": "Strong match",
>         "variant": "pill",
>         "tone": "primary",
>         "icon": "target"
>       }
>     },
>     {
>       "text": "Finding useful patterns",
>       "value": {
>         "text": "3 insights",
>         "variant": "badge",
>         "tone": "accent",
>         "icon": "sparkles"
>       }
>     },
>     {
>       "text": "Checking plan balance",
>       "value": {
>         "text": "Balanced",
>         "variant": "pill",
>         "tone": "neutral",
>         "icon": "sliders"
>       }
>     },
>     {
>       "text": "Verifying recommendations",
>       "value": {
>         "text": "Verified",
>         "variant": "text",
>         "tone": "success",
>         "icon": "shield"
>       }
>     },
>     {
>       "text": "Finalizing your plan",
>       "value": {
>         "text": "Ready",
>         "variant": "badge",
>         "tone": "primary",
>         "icon": "check-circle"
>       }
>     }
>   ],
>   "durationMs": 8000,
>   "style": "ring",
>   "showPercent": true,
>   "autoAdvance": false,
>   "footer": "Personalization complete. Your plan is ready.",
>   "footerLive": true
> }
> ```

## Popup

Type `Popup`. Opens a delayed offer or reminder. Modal bottom sheet that opens delayMs after the step shows (once); closes on scrim, close icon, Escape or dismissLabel, with focus trapped and restored. The primary button closes it, then goes next or opens url. Sends popup\_view once. Never put essential info only here.

| Setting | Type | Required | Description |
| - | - | - | - |
| `delayMs` | integer | no | Opens this many milliseconds after the step is shown (once; 1000-60000). There is no mouse "exit intent" on mobile, so a delay is the equivalent trigger Default: `8000`. |
| `icon` | "activity" \| "alarm" \| "apple" \| "award" \| "baby" \| "battery" \| "bed" \| "bike" \| "brain" \| "calendar" \| "check" \| "chart" \| "clock" \| "coffee" \| "dumbbell" \| "flame" \| "flower" \| "footprints" \| "gift" \| "heart" \| "leaf" \| "lightning" \| "lock" \| "moon" \| "mountain" \| "music" \| "person" \| "ruler" \| "salad" \| "scale" \| "shield" \| "smile" \| "sparkles" \| "star" \| "sun" \| "target" \| "thumbs-up" \| "timer" \| "trophy" \| "upload" \| "users" \| "water" \| "yoga" \| "x" \| "zap" \| "heart-pulse" \| "stethoscope" \| "pill" \| "syringe" \| "thermometer" \| "weight" \| "hand-heart" \| "eye" \| "ear" \| "bone" \| "carrot" \| "cherry" \| "cookie" \| "pizza" \| "sandwich" \| "egg" \| "fish" \| "beef" \| "wheat" \| "cup-soda" \| "wine" \| "candy" \| "soup" \| "milk" \| "waves" \| "medal" \| "flag" \| "goal" \| "bed-double" \| "cloud-moon" \| "sunrise" \| "sunset" \| "wind" \| "feather" \| "brain-circuit" \| "book-open" \| "headphones" \| "glasses" \| "home" \| "briefcase" \| "car" \| "plane" \| "map-pin" \| "globe" \| "dog" \| "cat" \| "shopping-bag" \| "wallet" \| "credit-card" \| "piggy-bank" \| "gamepad" \| "tv" \| "smartphone" \| "laptop" \| "camera" \| "palette" \| "graduation-cap" \| "smile-plus" \| "frown" \| "meh" \| "laugh" \| "angry" \| "heart-crack" \| "party-popper" \| "check-circle" \| "x-circle" \| "alert-triangle" \| "info" \| "help-circle" \| "plus" \| "minus" \| "arrow-right" \| "trending-up" \| "trending-down" \| "bar-chart" \| "pie-chart" \| "percent" \| "tag" \| "badge-check" \| "crown" \| "gem" \| "rocket" \| "key" \| "bell" \| "mail" \| "phone" \| "message-circle" \| "search" \| "settings" \| "sliders" \| "refresh" \| "infinity" \| "hourglass" \| "calendar-check" \| "list-checks" \| "clipboard" \| "file-text" \| "lightbulb" \| "puzzle" \| "magnet" \| "anchor" \| "user-check" \| "user-plus" \| "users-round" \| "handshake" \| "thumbs-down" \| "star-half" | no | Icon in the round badge at the top of the sheet Default: `"gift"`. |
| `title` | string | yes | Sheet headline, keep \<=40 chars; also the dialog's accessible name |
| `text` | string | no | Supporting copy, keep \<=120 chars |
| `buttonLabel` | string | no | Primary button text, keep \<=24 chars Default: `"Continue"`. |
| `action` | "next" \| "url" | no | next: close, validate the step and go on. url: close and open url Default: `"next"`. |
| `url` | string | no | Absolute URL opened by action "url"; required for it, ignored otherwise |
| `dismissLabel` | string | no | Secondary link under the button that just closes the sheet, e.g. "No thanks"; omit to leave only the close icon, scrim and Escape |

> [accordion] Example
>
> ```json theme={null}
> {
>   "delayMs": 3500,
>   "icon": "gift",
>   "title": "Your result is almost ready",
>   "text": "Finish the last step to unlock your personalised recommendations.",
>   "buttonLabel": "Continue",
>   "action": "next",
>   "dismissLabel": "Maybe later"
> }
> ```

## Analytics milestone

Type `AnalyticsMilestone`. Invisible scroll marker for long screens. Fires milestone\_view once when this exact position first enters the viewport; Amplitude exposes props.name as the event type. Use for report reveals, deep paywall sections and other persuasion milestones that are not separate steps.

| Setting | Type | Required | Description |
| - | - | - | - |
| `name` | string | yes | Analytics event name exposed to external product analytics when this position is first seen, e.g. report\_viewed or pricing\_details\_viewed; lowercase snake\_case only |

> [accordion] Example
>
> ```json theme={null}
> {
>   "name": "report_viewed"
> }
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.