Chapter 16
Markdown Style Demo
This page is a rendering fixture, not product documentation. Use it to review how the guides app styles common Markdown elements and custom docs features.
Inline text, links, and emphasis
A paragraph can mix strong text, emphasis, inline code, and links to another chapter. Inline code should stay compact enough to read in a sentence without looking like a full code block.
Raw HTML is escaped instead of rendered:
<section data-demo="escaped-html"> <p>This should appear as text, not as a real section.</p> </section>
Headings below the page title
The page title is the level-one heading. Chapter bodies should usually start at level two, but lower heading levels are available when a section needs more structure.
Level three heading
Use level three headings for subsections inside a page section.
Level four heading
Use level four headings sparingly. They work best when several neighboring subsections share the same shape.
Level five heading
Level five headings are available for dense reference sections.
Level six heading
Level six headings are the smallest heading style.
Lists and task states
Unordered lists work well for parallel facts:
- Web APIs are the baseline.
- Route maps own URL construction.
- Controllers return
Responseobjects.- Nested bullets keep supporting details near the parent item.
- Keep nesting shallow enough to scan.
Ordered lists work well when order matters:
- Receive a
Request. - Match the route.
- Run the controller.
- Return a
Response.
Task lists show checklist states:
- Render Markdown from a chapter file.
- Highlight code blocks.
- Replace these demo frames with real global frames later.
Blockquotes and rules
Frames render route-owned UI inside another page. They are useful when part of the page should fetch, reload, or hydrate independently from the surrounding document.
Horizontal rules separate examples that need a stronger break than paragraph spacing.
The content after the rule should still feel like part of the same chapter.
Tables
| Element | What to check | Example |
|---|---|---|
| Paragraph | Spacing and line length | Most prose in a guide |
| Inline code | Contrast and wrapping | context.render(...) |
| Table | Borders, padding, and scanability | This table |
| Frame | Margin around embedded UI | The examples below |
Images
Images should scale to the content width and keep rounded corners.
Code blocks with and without filenames
A plain code fence gets syntax highlighting and a copy button.
import { redirect } from "remix/response/redirect";
export async function createProject(request: Request) {
let formData = await request.formData();
let name = String(formData.get("name") ?? "").trim();
if (name === "") {
return new Response("Project name is required", { status: 400 });
}
return redirect(`/projects/${name}`, 303);
}A code fence can include a filename and highlighted lines in its metadata. The guides renderer pulls the filename into the header and marks the requested line ranges.
import type { AppContext } from "../../router.ts";
export async function createProject({ request }: AppContext) {
let formData = await request.formData();
let name = String(formData.get("name") ?? "").trim();
return new Response(`Created ${name || "untitled project"}`);
}Line highlighting also works with highlight= and lines= metadata if a fence already has other parameters.
let status = "idle";
let retries = 0;
if (status === "idle") {
retries++;
}Shell, JSON, and plain text fences use the same code block chrome.
pnpm --filter remix-guides run validate{
"scripts": {
"validate": "node --import remix/node-tsx scripts/validate-docs.ts"
}
}Plain text stays unhighlighted but still gets copy behavior.Markdown custom logic
Use an explicit heading ID when a section needs a stable URL that should not change with the visible text. The heading for this section uses {#markdown-custom-logic}.
The ::frame directive is docs-specific Markdown. Inside a code fence it stays text:
::frame{src="/examples/16-markdown-style-demo/callout/"}
```tsx filename=app/actions/docs/examples/16-markdown-style-demo/callout.demo.tsx
export function CalloutDemo() {
return () => <p>Hello from a frame.</p>;
}
```Outside a code fence, the renderer replaces the directive with a Remix <Frame>.
Reusable frame pieces without new directives
A one-off frame can still share structure. These examples use normal ::frame directives, but the examples export focused components instead of frame routes. The parent example handler hydrates the component, wraps it in a shared demo shell, and shows only the component source.
The counter demo uses browser events, but the example itself does not call clientEntry. It assumes the parent frame handler will hydrate it.
3
import { css, on } from "remix/ui";
import type { Handle } from "remix/ui";
export function Counter(handle: Handle) {
let count = 3;
return () => (
<div mix={counterStyles}>
<p mix={countStyles}>{count}</p>
<div mix={actionsStyles}>
<button
mix={[
buttonStyles,
on("click", () => {
count--;
handle.update();
}),
]}
type="button"
>
Decrement
</button>
<button
mix={[
buttonStyles,
on("click", () => {
count++;
handle.update();
}),
]}
type="button"
>
Increment
</button>
</div>
</div>
);
}
const counterStyles = css({
display: "grid",
gap: "var(--rmx-space-lg)",
placeItems: "center",
});
const countStyles = css({
margin: "0",
color: "var(--rmx-color-accent)",
fontSize: "calc(var(--rmx-font-size-page-title) * 2)",
fontWeight: "var(--rmx-font-weight-bold)",
lineHeight: "var(--rmx-line-height-tight)",
letterSpacing: "var(--rmx-letter-spacing-tight)",
});
const actionsStyles = css({
display: "flex",
flexWrap: "wrap",
gap: "var(--rmx-space-md)",
});
const buttonStyles = css({
appearance: "none",
border: "var(--rmx-space-px) solid var(--rmx-color-action-primary-border)",
borderRadius: "var(--rmx-radius-full)",
background: "var(--rmx-color-action-primary-background)",
color: "var(--rmx-color-action-primary-foreground)",
cursor: "pointer",
font: "inherit",
fontWeight: "var(--rmx-font-weight-bold)",
lineHeight: "1",
padding: "var(--rmx-space-sm) var(--rmx-space-lg)",
"&:hover": {
filter: "brightness(0.95)",
},
});
The callout is a plain server-rendered frame: no hydration, no source display, just the markup the handler returns.
Start with the route
Make the server response correct before adding browser behavior.
Add frames when a region has its own lifecycle
Frames keep the embedded UI route-owned while the surrounding guide stays plain Markdown.