OpenPress

@open-press/core

Press

A single document. <Press> declares its title, page geometry, sources, source root, and a React tree of Frames + utilities underneath it. Projects that follow folder conventions export a single Press from press/<slug>/press.tsx.

The <Press> component defines the core configuration, content sources, and component structure of a single document. The engine uses this declaration to establish isolated document boundaries within the Workspace.

1.0 Contract: The system relies on the press/*/press.tsx folder convention to discover publications. Each entry file must have a default export that returns a single <Press> instance, and the slug must match the folder name.

Component Impl

# <Press>

Configures and encapsulates the context, layout, and data sources for a single document. Serves as the host for `Frame`s and utility components during rendering.

import { Press } from "@open-press/core";
<Press
  title="..."
  page="a4" | "social-square" | "slide-16-9" | PageGeometry
  sources={[ mdxSource({ id, preset, root }) ]}
  slug?
  theme?
  componentsDir?
  mediaDir?
>
  {/* Frames + Manuscript Utilities */}
</Press>

Metadata & Routing

Name Type Default Description
title required string Full document name. Automatically bound to HTML ``, OG tags, PDF metadata, and the UI interface. This property is not directly output on the content canvas.</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">slug</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">URL route and output directory identifier. Automatically inferred from the outer directory name by default (e.g., `press/report` corresponds to `report`).</td> </tr> </tbody> </table> </div> </div><div class="min-w-0 max-w-full my-4 mb-6"> <p class="m-0 mb-2 text-subdued font-mono text-xs font-semibold tracking-[0.06em] uppercase">Resource Registration</p> <div class="max-w-full overflow-x-auto border border-hairline rounded-[6px]"> <table class="w-full min-w-[38rem] m-0 border-collapse text-sm leading-[1.55]"> <thead> <tr> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Name</th> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Type</th> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Default</th> <th class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top bg-paper-soft text-subdued-strong font-mono text-[0.72rem] font-semibold tracking-[0.06em] uppercase">Description</th> </tr> </thead> <tbody class="[&_tr:last-child_td]:border-b-0"> <tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">page</code> <span class="inline-block ml-[0.4rem] px-[0.45em] py-[0.05em] rounded-full bg-[color-mix(in_srgb,var(--op-accent)_12%,transparent)] text-accent font-mono text-[0.65rem] font-semibold tracking-[0.04em] uppercase">required</span> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">"a4" | "social-square" | "slide-16-9" | PageGeometry</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">Per-Press page geometry. Use generic presets for common formats, or pass a custom `{ id, label, width, height }` object for project-specific sizes. The exporter compiles the resolved geometry into `--openpress-page-*` CSS variables.</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">sources</code> <span class="inline-block ml-[0.4rem] px-[0.45em] py-[0.05em] rounded-full bg-[color-mix(in_srgb,var(--op-accent)_12%,transparent)] text-accent font-mono text-[0.65rem] font-semibold tracking-[0.04em] uppercase">required</span> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">SourceRegistration[]</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">List of data sources initialized by `mdxSource()`. The `id`s defined within are consumed by `<MdxArea>` and `<Sections>`.</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">theme</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">Theme path override for this Press. New work should keep tokens and font loading under `press/<slug>/theme/`; use shared theme paths only for intentional multi-Press shared source.</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">componentsDir</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string | string[]</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">Physical directory for auto-loaded MDX components. Defaults to `./components`. Components in the directory can be used in MDX without an `import`.</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">mediaDir</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">string | string[]</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">Local media files directory. Defaults to `./media`.</td> </tr><tr> <td class="whitespace-nowrap px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="bg-transparent p-0 text-ink-strong font-mono text-[0.85rem] font-semibold">children</code> <span class="inline-block ml-[0.4rem] px-[0.45em] py-[0.05em] rounded-full bg-[color-mix(in_srgb,var(--op-accent)_12%,transparent)] text-accent font-mono text-[0.65rem] font-semibold tracking-[0.04em] uppercase">required</span> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <code class="px-[0.32em] py-[0.06em] bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] rounded-[3px] font-mono text-[0.85rem] text-subdued-strong">ReactNode</code> </td> <td class="px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top"> <span aria-hidden="true">—</span> </td> <td class="text-ink px-[0.85rem] py-[0.6rem] border-b border-hairline text-left align-top [&_code]:px-[0.3em] [&_code]:py-[0.06em] [&_code]:rounded-[3px] [&_code]:bg-[color-mix(in_srgb,var(--op-ink)_6%,transparent)] [&_code]:font-mono [&_code]:text-[0.85rem]">The tree structure of `<Frame>`s and utility components (like `<Toc>`, `<Sections>`) that make up the document body.</td> </tr> </tbody> </table> </div> </div><h3 id="example-declaring-a-single-document">Example: Declaring a Single Document</h3><pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { Press, Frame } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> "@open-press/core"</span><span style="color:#E1E4E8">;</span></span> <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { mdxSource } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> "@open-press/core/mdx"</span><span style="color:#E1E4E8">;</span></span> <span class="line"><span style="color:#F97583">import</span><span style="color:#E1E4E8"> { Sections, Toc } </span><span style="color:#F97583">from</span><span style="color:#9ECBFF"> "@open-press/core/manuscript"</span><span style="color:#E1E4E8">;</span></span> <span class="line"></span> <span class="line"><span style="color:#F97583">export</span><span style="color:#F97583"> default</span><span style="color:#F97583"> function</span><span style="color:#B392F0"> ReportPress</span><span style="color:#E1E4E8">() {</span></span> <span class="line"><span style="color:#F97583"> return</span><span style="color:#E1E4E8"> (</span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Press</span></span> <span class="line"><span style="color:#B392F0"> title</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Transport models in dense networks"</span></span> <span class="line"><span style="color:#B392F0"> page</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"a4"</span></span> <span class="line"><span style="color:#B392F0"> sources</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{[</span></span> <span class="line"><span style="color:#B392F0"> mdxSource</span><span style="color:#E1E4E8">({ id: </span><span style="color:#9ECBFF">"story"</span><span style="color:#E1E4E8">, preset: </span><span style="color:#9ECBFF">"section-folders"</span><span style="color:#E1E4E8">, root: </span><span style="color:#9ECBFF">"report/chapters"</span><span style="color:#E1E4E8"> }),</span></span> <span class="line"><span style="color:#E1E4E8"> ]}</span></span> <span class="line"><span style="color:#E1E4E8"> ></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Frame</span><span style="color:#B392F0"> frameKey</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"cover"</span><span style="color:#B392F0"> role</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"document.cover"</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Cover</span><span style="color:#E1E4E8"> /></span></span> <span class="line"><span style="color:#E1E4E8"> </</span><span style="color:#79B8FF">Frame</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Toc</span><span style="color:#B392F0"> source</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"story"</span><span style="color:#B392F0"> maxLevel</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{</span><span style="color:#79B8FF">2</span><span style="color:#E1E4E8">} /></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Sections</span><span style="color:#B392F0"> source</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"story"</span><span style="color:#E1E4E8"> /></span></span> <span class="line"><span style="color:#E1E4E8"> </</span><span style="color:#79B8FF">Press</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> );</span></span> <span class="line"><span style="color:#E1E4E8">}</span></span></code></pre><h3 id="example-custom-project-geometry">Example: Custom Project Geometry</h3><pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8;overflow-x:auto" tabindex="0" data-language="tsx"><code><span class="line"><span style="color:#E1E4E8"><</span><span style="color:#79B8FF">Press</span></span> <span class="line"><span style="color:#B392F0"> slug</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"campaign"</span></span> <span class="line"><span style="color:#B392F0"> title</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"Campaign Card"</span></span> <span class="line"><span style="color:#B392F0"> page</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">{{ id: </span><span style="color:#9ECBFF">"campaign-card"</span><span style="color:#E1E4E8">, label: </span><span style="color:#9ECBFF">"Campaign Card"</span><span style="color:#E1E4E8">, width: </span><span style="color:#9ECBFF">"1080px"</span><span style="color:#E1E4E8">, height: </span><span style="color:#9ECBFF">"1350px"</span><span style="color:#E1E4E8"> }}</span></span> <span class="line"><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">Frame</span><span style="color:#B392F0"> frameKey</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"cover"</span><span style="color:#B392F0"> role</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"social.card"</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"> <</span><span style="color:#79B8FF">CampaignCard</span><span style="color:#E1E4E8"> /></span></span> <span class="line"><span style="color:#E1E4E8"> </</span><span style="color:#79B8FF">Frame</span><span style="color:#E1E4E8">></span></span> <span class="line"><span style="color:#E1E4E8"></</span><span style="color:#79B8FF">Press</span><span style="color:#E1E4E8">></span></span></code></pre> </div> </section> <h2 id="runtime-contract-and-constraints">Runtime Contract and Constraints</h2> <p>The engine adheres to the following invariants when processing <code><Press></code>:</p> <ul> <li><strong>Single Root Rule</strong>: All visible and structural content of the document must be descendants of a single <code><Press></code>.</li> <li><strong>Configuration Consistency</strong>: Document settings are entirely encapsulated in props; there are no parallel frontend config files.</li> <li><strong>Order Constraints</strong>: The order of top-level components in the tree structure (like cover, TOC, body paragraphs) directly corresponds to the output page order.</li> <li><strong>Stateless Rendering</strong>: The rendering phase utilizes multiple passes (calculating space, arranging blocks, etc.); developers must not trigger side effects (like network reads/writes, random number generation, or cache operations) within the component tree.</li> </ul> </div> </article> </main> <footer class="border-t border-hairline-strong bg-[linear-gradient(90deg,rgba(15,13,10,0.035)_1px,transparent_1px),var(--op-paper-soft)] bg-[size:3rem_3rem] py-10"> <div class="op-page grid grid-cols-[1fr_2fr_1fr] gap-8 items-center max-[720px]:grid-cols-1 max-[720px]:text-center"> <div> <p class="font-display text-xl text-ink-strong">OpenPress</p> <p class="op-meta">Agent-first document package · v1.0</p> </div> <ul class="flex flex-wrap justify-center gap-5 p-0 m-0 list-none text-sm [&>li>a]:text-ink [&>li>a]:no-underline hover:[&>li>a]:text-accent"> <li><a href="https://open-press-story.pages.dev">Document</a></li> <li><a href="/showcase">Showcase</a></li> <li><a href="https://github.com/quan0715/open-press">GitHub</a></li> <li><a href="https://www.npmjs.com/package/@open-press/cli">@open-press/cli</a></li> <li><a href="https://github.com/quan0715/open-press/issues">Issues</a></li> </ul> <p class="text-right max-[720px]:text-center op-meta">MIT License · 2026</p> </div> </footer> </body></html>