Alpha: Ships on npm at 7.0.0-alpha.07.0.0-alpha.0
api-product
Say what a Business sells: hero, proof, features, plans and a closing, in the order a buyer reads them.
Theme
VanrosterSaaS
callboardown8 sections
DiffkeelDeveloper tool
certificateown8 sections
Harbor tidesAPI product
quaypointown14 sections
7 pages in 4 Businesses name api-product in this build.
Use api-product for the pages that sell: a landing and a pricing page. The hero states the offer and its two actions, proof and features follow in the order a buyer asks, OfferTiles price it, and a closing repeats the one action that matters. Despite the name, it fits any Product or Service, not only an API.
Sections in order
Harbor tides /
Fields that matter
| Section type | Field | Type | Bounds | Refused as |
|---|---|---|---|---|
| hero | title | string ≤64 | 1–64 | hero.title |
| hero | lede | string ≤260 | 1–260 | hero.lede |
| hero | actions | 0–2 × object | ≤2 | hero.actions |
| feature-grid | items | 2–8 × object | 2–8 | feature-grid.items |
| pricing | tiers | 1–4 × object | 1–4 | pricing.tiers |
| pricing | tiers[].features | 1–10 × string ≤56 | 1–10 | pricing.tiers.features |
Every field of hero
| Field | Type | Req | Bounds | Refusal |
|---|---|---|---|---|
layout | "centered" | "split" | yes | — | |
stack | 560 | 640 | yes | — | |
kicker | object | — | ||
kicker.kind | "eyebrow" | yes | — | |
kicker.text | string ≤40 | yes | 1–40 | hero.kicker.text |
title | string ≤64 | yes | 1–64 | hero.title |
lede | string ≤260 | yes | 1–260 | hero.lede |
actions | 0–2 × object | yes | ≤2 | hero.actions |
actions[].href | string ≤160 | yes | 1–160 | hero.actions.href |
actions[].label | string ≤24 | yes | 1–24 | hero.actions.label |
actions[].kind | "solid" | "ghost" | yes | — | |
actions[].icon | icon or mark name (52) | — | ||
actions[].external | boolean | — | ||
command | object | — | ||
command.command | string ≤40 | yes | 1–40 | hero.command.command |
command.note | string ≤48 | 1–48 | hero.command.note | |
fine | 0–4 × string ≤72 | yes | ≤4 | hero.fine |
specimen | object | — | ||
specimen.panel | "none" | "field" | — | ||
specimen.blocks | 1–3 × Section type (nested) | yes | 1–3 | hero.specimen.blocks |
Every field of feature-grid
| Field | Type | Req | Bounds | Refusal |
|---|---|---|---|---|
head | object | — | feature-grid.head | |
head.eyebrow | string ≤28 | yes | 1–28 | feature-grid.head.eyebrow |
head.title | string ≤72 | 1–72 | feature-grid.head.title | |
head.lede | string ≤300 | 1–300 | feature-grid.head.lede | |
head.max | string (pattern) | — | ||
head.ledeMax | string (pattern) | — | ||
head.balance | boolean | — | ||
head.scale | "compact" | "large" | — | ||
head.tag | string ≤8 | 1–8 | feature-grid.head.tag | |
head.layout | "split" | — | ||
aside | object | — | ||
aside.command | string ≤12 | yes | 1–12 | feature-grid.aside.command |
aside.note | string ≤40 | yes | 1–40 | feature-grid.aside.note |
cellHead | "label" | "icon-index" | "icon-label" | "icon" | "none" | yes | — | |
dense | boolean | — | ||
items | 2–8 × object | yes | 2–8 | feature-grid.items |
items[].icon | icon or mark name (52) | — | ||
items[].label | string ≤24 | 1–24 | feature-grid.items.label | |
items[].index | string ≤2 | 1–2 | feature-grid.items.index | |
items[].title | string ≤40 | 1–40 | feature-grid.items.title | |
items[].text | string ≤200 | 1–200 | feature-grid.items.text | |
items[].href | string ≤160 | 1–160 | feature-grid.items.href | |
items[].linkLabel | string ≤24 | 1–24 | feature-grid.items.linkLabel |
Every field of pricing
| Field | Type | Req | Bounds | Refusal |
|---|---|---|---|---|
head | object | yes | — | pricing.head |
head.eyebrow | string ≤28 | yes | 1–28 | pricing.head.eyebrow |
head.title | string ≤72 | 1–72 | pricing.head.title | |
head.lede | string ≤300 | 1–300 | pricing.head.lede | |
head.max | string (pattern) | — | ||
head.ledeMax | string (pattern) | — | ||
head.balance | boolean | — | ||
head.scale | "compact" | "large" | — | ||
head.tag | string ≤8 | 1–8 | pricing.head.tag | |
head.layout | "split" | — | ||
layout | "ruled" | "cards" | yes | — | |
tiers | 1–4 × object | yes | 1–4 | pricing.tiers |
tiers[].name | string ≤16 | yes | 1–16 | pricing.tiers.name |
tiers[].price | string ≤16 | yes | 1–16 | pricing.tiers.price |
tiers[].note | string ≤24 | 1–24 | pricing.tiers.note | |
tiers[].desc | string ≤80 | 1–80 | pricing.tiers.desc | |
tiers[].features | 1–10 × string ≤56 | yes | 1–10 | pricing.tiers.features |
tiers[].cta | string ≤40 | yes | 1–40 | pricing.tiers.cta |
tiers[].href | string ≤160 | yes | 1–160 | pricing.tiers.href |
tiers[].featured | boolean | — | ||
featuredBadge | string ≤16 | yes | 1–16 | pricing.featuredBadge |
note | string ≤120 | 1–120 | pricing.note |
Starter record
pages/vanroster/home.json8.7 KB api-product.json{
"$schema": "https://next.mdxui.dev/schema.json",
"theme": "callboard",
"frame": "none",
"template": "api-product",
"title": "Vanroster: every crew on the right job",
"description": "Crew scheduling for field-service teams. Vanroster matches each job to a crew with the skills, the van and the shortest drive.",
"sections": [
{
"kind": "nav",
"shell": {"inset":"bleed","density":"none","surface":"glass","sticky":true},
"block": {
"type": "nav",
"menu": "push",
"collapse": 880,
"brand": {"href":"/","text":"Vanroster","mark":"radar"},
"links": [
["/product/dispatch","Dispatch"],
["/pricing","Pricing"],
["/changelog","Changelog"],
["/security","Security"]
],
"cta": {"href":"/contact","label":"Book a walkthrough"}
}
},
{
"kind": "hero",
"shell": {"inset":"contained","density":"hero","background":"glow"},
"block": {
"type": "hero",
"layout": "split",
"stack": 640,
"title": "Every crew on the right job.",
"lede": "Vanroster matches each job to a crew with the right skills, the right van and the shortest drive. Move a job on the board, and every crew phone follows.",
"actions": [
{"href":"/contact","label":"Book a walkthrough","kind":"solid"},
{"href":"/pricing","label":"See the plans","kind":"ghost"}
],
"fine": ["14-day trial, no payment details","Office users free on every plan"],
"specimen": {
"panel": "field",
"blocks": [
{
"type": "terminal",
"variant": "plain",
"title": "Board · Tue 13 Oct · 07:42",
"lines": [
"@dim{CREW VAN JOB WINDOW DRIVE}",
"A-1 V-12 Boiler service 08:00-10:00 14 min",
"A-1 V-12 Heat pump install 10:30-13:00 9 min",
"@dim{B-2 V-07 Out today: 2 jobs moved}",
"C-3 V-03 Panel upgrade 08:00-11:00 22 min",
"C-3 V-03 @acc{Boiler service} ←B2 11:30-12:30 @pass{6 min}",
"D-4 V-15 @acc{Gas leak check} ←B2 13:00-14:00 @pass{11 min}",
"D-4 V-15 Water heater swap 14:30-16:30 18 min",
"",
"@pass{2 customers texted a new window}"
]
}
]
}
}
},
{
"kind": "band",
"shell": {"inset":"contained","density":"compact"},
"block": {
"type": "stats",
"items": [
{"value":"400","label":"Crews on one board, on every plan","icon":"layout-grid"},
{"value":"12","label":"Skills a job can require, from gas to height","icon":"badge-check"},
{"value":"30 min","label":"Arrival window texted to each customer","icon":"clock"}
]
}
},
{
"kind": "section",
"shell": {"inset":"contained","density":"section"},
"block": {
"type": "feature-grid",
"cellHead": "icon",
"head": {
"eyebrow": "The board",
"title": "One board runs the whole day.",
"lede": "Office staff plan on the board. Crews work from one ordered list on their phones."
},
"items": [
{
"icon": "map-pin",
"title": "Drive time, not distance",
"text": "Jobs are ordered by the drive between them, from road times at the hour of the job."
},
{
"icon": "badge-check",
"title": "Skills before speed",
"text": "A gas job reaches only a crew with a gas licence on file and in date."
},
{
"icon": "refresh-cw",
"title": "Swaps in one drag",
"text": "Drag a job to another crew. Both crew phones update before the drag ends."
},
{
"icon": "bell",
"title": "Customers told once",
"text": "Each customer gets one text with a 30-minute window, and one when the van leaves."
},
{
"icon": "clock",
"title": "Overtime flagged",
"text": "A day that keeps a crew past 10 hours turns amber before anyone assigns it."
},
{
"icon": "shield-check",
"title": "Every change logged",
"text": "Who moved which job, when and why, kept for up to 7 years."
}
]
}
},
{
"kind": "section",
"shell": {"inset":"contained","density":"section","surface":"tint"},
"block": {
"type": "feature-split",
"head": {"eyebrow":"A sick call at 07:40","title":"Re-plan the day in one move."},
"items": [
{
"icon": "x",
"title": "Mark the crew out",
"text": "One tap clears the crew and lists every job it held that day."
},
{
"icon": "radar",
"title": "Accept the re-plan",
"text": "Each job goes to the nearest crew that holds every skill it needs."
},
{
"icon": "message-square",
"title": "Tell the customers",
"text": "Each changed window goes out as one text, with the new time."
}
],
"specimen": {
"blocks": [
{
"type": "terminal",
"variant": "plain",
"title": "Text to the customer · 07:43",
"lines": [
"Your boiler service today moved to 11:30-12:30.",
"Crew C-3 is coming in van V-03.",
"",
"@dim{A second text arrives when the van leaves.}",
"@dim{Reply 1 to keep the time, 2 to pick another day.}"
]
}
]
}
}
},
{
"kind": "section",
"shell": {"inset":"contained","density":"section"},
"block": {
"type": "compare",
"head": {"eyebrow":"Against the whiteboard","title":"A board that moves with the day."},
"cons": {
"label": "Whiteboard and calls",
"items": [
"Jobs assigned from memory",
"Licences checked by phone",
"Customers call to ask",
"Swaps lost by noon"
]
},
"pros": {
"label": "Vanroster",
"items": [
"Jobs matched to skills",
"Licences checked on assign",
"Customers get a window",
"Every swap on record"
]
}
}
},
{
"kind": "closing",
"shell": {"inset":"contained","density":"closing","surface":"plate"},
"block": {
"type": "closing",
"layout": "split",
"head": {
"eyebrow": "14-day trial",
"title": "Plan tomorrow on Vanroster.",
"lede": "Import crews and jobs from three spreadsheets. The first board is ready the same afternoon."
},
"specimen": {
"blocks": [
{
"type": "terminal",
"variant": "plain",
"title": "Import · 3 files",
"lines": [
"crews.csv 14 crews, 14 vans, 2 depots",
"skills.csv 38 licences, @acc{3 expire this year}",
"jobs.csv 212 jobs over the next 10 days",
"",
"@pass{Board ready: 212 jobs on 14 crews}"
]
}
]
},
"actions": [
{"href":"/contact","label":"Book a walkthrough","kind":"solid"},
{"href":"/product/dispatch","label":"Tour Dispatch","kind":"ghost"}
]
}
},
{
"kind": "footer",
"shell": {"inset":"contained","density":"footer"},
"block": {
"type": "footer",
"layout": "areas",
"brand": {"href":"/","text":"Vanroster","mark":"radar"},
"blurb": "Crew scheduling for field-service teams: heating, electrical, plumbing, cleaning and grounds.",
"columns": [
{
"title": "Product",
"links": [["/product/dispatch","Dispatch"],["/pricing","Pricing"],["/changelog","Changelog"]]
},
{"title":"Company","links":[["/security","Security"],["/contact","Contact"]]},
{
"title": "For agents",
"links": [["/product/dispatch","The Dispatch API"],["/records.zip","Site records (.zip)"]]
}
],
"cta": {"href":"/contact","label":"Book a walkthrough","icon":"arrow-right"},
"note": [
"Vanroster is a worked example on mdxui.dev, not a customer.",
"Rendered by @mdxui/site. Forms here do not send."
]
}
}
]
}