Browse the docs ▾

Tutorials

Build an interactive landing page

Tabs, accordions, carousels and section links need no code. Page state, handlers and display conditions cover them.

The brief: "One page for our yoga retreat: what's included, a pricing section with tabs, an FAQ, reviews and a WhatsApp button."
You'll end with one responsive page whose interactions work without any custom scripts.

How interaction works

Each page has state: named values that start empty and carry over to other pages in the same visit. Handlers change state on a click or hover. Display conditions read it, to show or hide a node. get_element_catalogue lists the actions, conditions and patterns under interactivity.

1. Lay out the sections

Build the page in order: hero, included, pricing, reviews, FAQ, final call to action. Give any section you want to jump to an id with set_element_id.

2. Add section links

A click on a container or button can run scrollIntoView {id}. For a link from another page, use an address like /#pricing. Handlers go on containers and buttons, not links, so wrap a link in a container if you need one.

3. Build tabs

Give each tab button a self.updateState handler, such as {tab: "group"}. Give each panel a display condition with the same state value, and a default for the first tab, so one panel shows before any click. To style the active tab, duplicate the button and show one of the two by the same condition. A link can open a tab directly with ?state.tab=group.

4. Build an FAQ accordion

Build each question from its own container. Its header takes self.toggleFromList {list_name: "faq_open", id: "q1"}, and its answer takes the condition {in_list: "faq_open", id: "q1"}.
Don't build the accordion as a list with cards. Inside a card every row shares the same id, so all of them would open together. For one-open-at-a-time, use self.updateState {faq: "q1"} and the condition {state: "faq", value: "q1"}.

5. Add a carousel of reviews

Add one with add_element kind: "carousel". It starts with an empty card: add a quote and a name to it, and bind_field them to listData.quote and listData.name. set_list_items writes the rows, and set_settings controls cards per screen, gap, autoplay, interval and controls_type. Use only reviews the owner gave you. Never write your own.

6. Add calls to action

Use a button with a https://wa.me/... or tel: address, or a link to the owner's booking service. Add track_event {event_action: "whatsapp_click"} to count clicks in the site's Google Analytics, if the owner has set that up. Analytics' own names, such as purchase, are refused.

7. Polish with hover

set_style with breakpoint: "hover" changes a style while the pointer is over a node, and transition animates it. Phones have no hover, so never hide anything important behind it. Give buttons and links the same look with breakpoint: "focus", so people using a keyboard can see where they are.

8. Check every state

A plain screenshot shows only the starting state. Pass state to see the others: {"tab": "group"}, or {"faq": "q1"}. Lists can't be set this way, so the faq_open pattern is best checked in the owner's browser. Take each at phone width too.

What isn't supported

Scroll effects

No animation on scroll, parallax or custom scripts.

YouTube chapters

A click can jump a video file to a time with seekVideo, but not a YouTube or Vimeo video. In a list's card, seconds: "{listData.seconds}" takes each row's own time.

Outside requests

Handlers can't call other services.

Pricing tabs

{ "op": "set_handlers",
  "ref": "home/pricing/tab-group",
  "handlers": [{
    "event": "click",
    "action": "self.updateState",
    "params": { "tab": "group" } }] }

{ "op": "set_display_condition",
  "ref": "home/pricing/panel-group",
  "condition": {
    "state": "tab",
    "value": "group",
    "default": "solo" } }

FAQ accordion

{ "op": "set_handlers",
  "ref": "home/faq/q1-head",
  "handlers": [{
    "event": "click",
    "action": "self.toggleFromList",
    "params": {
      "list_name": "faq_open",
      "id": "q1" } }] }

{ "op": "set_display_condition",
  "ref": "home/faq/q1-answer",
  "condition": {
    "in_list": "faq_open",
    "id": "q1" } }

See another state

{
  "name": "get_page_screenshot",
  "arguments": {
    "org_id": 1042,
    "page": "home",
    "state": { "tab": "group" },
    "breakpoint": "mobile"
  }
}