Introduction
mdxui renders a bounded JSON record as a page under any Theme.
An agent or a person writes the record. Section types, drawn once by hand, lay it out, and a Theme paints it in one Business's Brand. The Harbor tides landing, one record under two Themes:
{
"$schema": "https://next.mdxui.dev/schema.json",
"theme": "quaypoint",
"template": "api-product",
"title": "Harbor tides: every berth, every tide",
"description": "Tide tables for every berth in the harbor, as one API: heights, times and the window your keel clears.",
"frame": "none",
"sections": [
{
"kind": "nav",
"shell": { "inset": "bleed", "density": "none", "surface": "glass", "sticky": true },
"block": {
"type": "nav",
"menu": "none",
"brand": { "href": "/", "text": "Harbor tides", "mark": "waves" },
"links": [
["/pricing", "Pricing"],
["/integrations", "Integrations"],
["/blog", "Blog"]
],
"cta": { "href": "/contact", "label": "Talk to the harbor" }
}
},
{
"kind": "hero",
"shell": { "inset": "contained", "density": "hero" },
"block": {
"type": "hero",
"layout": "centered",
"stack": 560,
"title": "Every berth, every tide.",
"lede": "One call returns the tide table for any berth in the harbor: the times, the heights and the window your keel clears.",
"actions": [
{ "href": "/pricing", "label": "See the plans", "kind": "solid" },
{ "href": "/contact", "label": "Talk to the harbor", "kind": "ghost" }
],
"fine": ["1,000 calls a month free", "Heights to the chart datum"]
}
},
{
"kind": "band",
"shell": { "inset": "contained", "density": "compact" },
"block": { "type": "command-row", "command": "npm i harbor-tides", "note": "Node.js 22 or later" }
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "feature-grid",
"cellHead": "icon",
"head": { "eyebrow": "One call returns", "title": "The whole tide, per berth." },
"items": [
{ "icon": "waves", "title": "High and low water", "text": "Times and heights for 14 days, at the berth itself." },
{ "icon": "anchor", "title": "Keel clearance", "text": "The hours a given draft clears, from the charted depth." },
{ "icon": "map-pin", "title": "Datum named", "text": "Every height names its chart datum." }
]
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section", "surface": "plate" },
"block": {
"type": "api-call",
"method": "GET",
"path": "/v1/berths/{berth}/tides",
"note": "One berth, 14 days",
"examples": [
{
"label": "Pier 4, today",
"request": "curl https://api.harbor-tides.example/v1/berths/pier-4/tides?days=1",
"status": "200 OK",
"response": "{\n \"berth\": \"pier-4\",\n \"datum\": \"chart datum\",\n \"tides\": [\n { \"at\": \"04:12\", \"type\": \"low\", \"m\": 0.6 },\n { \"at\": \"10:31\", \"type\": \"high\", \"m\": 4.1 },\n { \"at\": \"16:40\", \"type\": \"low\", \"m\": 0.8 },\n { \"at\": \"22:58\", \"type\": \"high\", \"m\": 3.9 }\n ]\n}"
}
]
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "endpoints",
"head": { "eyebrow": "The API", "title": "Four routes, one shape." },
"rows": [
{ "method": "GET", "path": "/v1/berths", "text": "Every berth in a harbor, with its charted depth" },
{ "method": "GET", "path": "/v1/berths/{berth}/tides", "text": "High and low water for one berth" },
{ "method": "GET", "path": "/v1/berths/{berth}/window", "text": "The hours a given draft clears" },
{ "method": "POST", "path": "/v1/alerts", "text": "A webhook before a window closes" }
]
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "feature-split",
"head": { "eyebrow": "For the bridge", "title": "Plan the passage, not the math." },
"items": [
{ "icon": "clock", "title": "Windows, not tables", "text": "Ask for a draft and get the hours it clears." },
{ "icon": "bell", "title": "Alerts before the turn", "text": "A webhook fires an hour before a window closes." },
{ "icon": "shield-check", "title": "One source per berth", "text": "Each answer names its station and its datum." }
],
"specimen": {
"blocks": [
{
"type": "terminal",
"variant": "plain",
"title": "A 3.2 m draft at pier 4",
"lang": "json",
"lines": ["{", " \"draft\": 3.2,", " \"opens\": \"08:05\",", " \"closes\": \"12:50\"", "}"]
}
]
}
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "bento",
"head": { "eyebrow": "Where it runs", "title": "In the tools a crew opens." },
"panels": [
{
"title": "In a script",
"text": "One package, typed.",
"specimen": {
"type": "terminal",
"variant": "plain",
"title": "window.ts",
"lang": "ts",
"lines": ["const w = await tides.window('pier-4', 3.2);"]
}
},
{
"title": "In an agent",
"text": "One prompt, one berth.",
"specimen": {
"type": "agent-prompt",
"label": "Ask an agent",
"lines": ["Fetch the tide window for a 3.2 m draft at pier 4 today."]
}
},
{
"title": "On a chart plotter",
"text": "Through the Chartplot integration.",
"specimen": {
"type": "api-call",
"method": "POST",
"path": "/v1/alerts",
"examples": [
{ "label": "Alert", "request": "{ \"berth\": \"pier-4\", \"draft\": 3.2 }", "status": "201 Created", "response": "{ \"id\": \"al_42\" }" }
]
}
}
]
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "compare",
"head": { "eyebrow": "Against station tables", "title": "The berth, not the nearest station." },
"cons": {
"label": "Station tables",
"items": ["Heights at the station", "One PDF per month", "Datum in the small print", "Math done by hand"]
},
"pros": {
"label": "Harbor tides",
"items": ["Heights at the berth", "Fourteen days per call", "Datum on every height", "The window computed"]
}
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "steps",
"head": { "eyebrow": "How it works", "title": "From berth to window." },
"steps": ["Name the berth", "Give the draft", "Read the window", "Set an alert before it closes"]
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "text-stack",
"layout": "stack",
"head": { "eyebrow": "Where the numbers come from", "title": "Charted, measured, named." },
"items": [
{ "title": "Charted depth", "text": "Each berth carries the depth from the latest harbor survey." },
{ "title": "Measured tide", "text": "Predictions are corrected every 6 minutes from the harbor gauge." },
{ "title": "Named datum", "text": "Every height says which datum it stands on." }
]
}
},
{
"kind": "section",
"shell": { "inset": "contained", "density": "section" },
"block": {
"type": "agent-prompt",
"label": "Paste into your agent",
"lines": ["Install harbor-tides from npm.", "Fetch the tide window for a 3.2 m draft at pier 4 today.", "Print when it opens and when it closes."]
}
},
{
"kind": "closing",
"shell": { "inset": "contained", "density": "closing" },
"block": {
"type": "closing",
"layout": "stack",
"head": { "eyebrow": "Free to start", "title": "Ask for your berth.", "lede": "A harbor master adds any berth in the harbor within one working day." },
"actions": [
{ "href": "/contact", "label": "Talk to the harbor", "kind": "solid" }
]
}
},
{
"kind": "footer",
"shell": { "inset": "contained", "density": "footer" },
"block": {
"type": "footer",
"layout": "row",
"brand": { "href": "/", "text": "Harbor tides", "mark": "waves" },
"blurb": "Tide tables for every berth, as one API.",
"columns": [
{
"title": "Product",
"links": [
["/pricing", "Pricing"],
["/integrations", "Integrations"]
]
},
{
"title": "Harbor",
"links": [
["/blog", "Blog"],
["/contact", "Contact"],
["/terms", "Terms"]
]
}
],
"note": ["Harbor tides is a worked example on mdxui.dev.", "Rendered by @mdxui/site."]
}
}
]
}
Choose a path
| Path | You have | Start | You leave with |
|---|---|---|---|
| Build | A project directory | Quickstart | One record as two HTML files, under two Themes |
| Agent | A coding agent | For agents | A record the agent wrote that parses, and two renders |
| Brand | A Brand's colors and typefaces | Write a Theme | Your client's Theme, rendering every record |
| Ship | Records that parse | Ship a Site | A live Site with its 404 page and sitemap |
For a client site, take them in order: Quickstart (5 minutes), Write a Theme (an afternoon), Ship a Site (one deploy). Start from the record closest to your page:
curl -O https://next.mdxui.dev/pages/harbor/home.jsonWhat a record holds
A Page is a JSON record of strings, counts and names from closed sets, with no markup, class names or CSS. Every string has a bound, so a record renders as drawn or names the field to shorten.
Bounds to plan around
- A brand is a
textwordmark of up to 16 characters, or one of the 52 icon marks. - Section types hold text, code and icons; no field holds a picture.
renderPagetakes the 5 bundled Themes;rendertakes yours.ship.tsbuilds the whole Site.- A string over its bound is refused, never wrapped.
- Apps are coming.
The packages
@mdxui/site holds the schema, the renderer and the Section types; @mdxui/themes holds the Theme contract and the 5 Themes. Packages and versions pins both.
Where the definitions live
startupbuilder.dev defines the Site, Page, Theme, Template and Component. These docs show how mdxui renders each one.
The Start guides
Run Quickstart first. Ship a Site runs beside Write a Theme in the same project.
| Page | Result |
|---|---|
| Quickstart | One Page record as two HTML files under two Themes, and one refusal read |
| Ship a Site | Every record in pages/ as one Site under one Theme, with its 404, sitemap and robots file, served from Cloudflare |
| For agents | The prompt an agent runs to write a record that parses |
| Packages and versions | The pinned pair to install, and what each dist-tag points at |